Explorar o código

fix(sub): keep serverNames out of a reality host's JSON client config (#6691)

* fix(sub): keep serverNames out of a reality host's JSON client config

A host with an SNI on a REALITY inbound set both serverName and the
server-side serverNames list on the per-host stream. Both the JSON and
Clash renderers have already reduced the stream to its client form by
then, so serverNames was never read, and the JSON subscription shipped
it in the proxy outbound. xray-core refuses to start that config
("non-empty serverNames, please use serverName instead"), which breaks
every JSON-subscription client on the inbound. Set serverName only.

Fixes #6690

* docs(sub): keep the host reality SNI comments within two lines

Same two-line comment cap the #6694 review applied.
Farhan Zare hai 13 horas
pai
achega
3168c87c67
Modificáronse 2 ficheiros con 57 adicións e 3 borrados
  1. 2 3
      internal/sub/host_sub.go
  2. 55 0
      internal/sub/host_sub_test.go

+ 2 - 3
internal/sub/host_sub.go

@@ -162,14 +162,13 @@ func applyHostStreamOverrides(ep map[string]any, stream map[string]any) {
 			}
 		}
 	}
-	// Reality SNI override (host only): JSON realityData reads serverNames and
-	// clash reads serverName, so set both forms.
+	// Reality SNI override (host only): the stream is already in client form, and xray
+	// refuses a reality client carrying the server-side serverNames list (#6690).
 	if isHostEndpoint(ep) {
 		if sec, _ := stream["security"].(string); sec == "reality" {
 			if rs, ok := stream["realitySettings"].(map[string]any); ok && rs != nil {
 				if sni, ok := externalProxySNI(ep); ok {
 					rs["serverName"] = sni
-					rs["serverNames"] = []any{sni}
 				}
 			}
 		}

+ 55 - 0
internal/sub/host_sub_test.go

@@ -480,6 +480,61 @@ func TestSub_HostRealitySniOverride(t *testing.T) {
 	}
 }
 
+// A reality host's SNI reaches JSON and Clash as serverName only: xray refuses a
+// reality client that also carries serverNames (#6690).
+func TestSub_HostRealitySniJSONAndClash(t *testing.T) {
+	seedSubDB(t)
+	realityStream := `{"network":"tcp","security":"reality","tcpSettings":{"header":{"type":"none"}},"realitySettings":{"serverNames":["base.reality.com"],"shortIds":["abcd"],"settings":{"publicKey":"PBK","fingerprint":"chrome"}}}`
+	ib := seedSubInbound(t, "s1", "rlj", 4491, 1, realityStream)
+	seedHost(t, &model.Host{
+		InboundId: ib.Id, SortOrder: 0, Remark: "RLJ", Address: "rl.cdn.com", Port: 8443,
+		Security: "reality", Sni: "host.reality.com",
+	})
+
+	out, _, err := NewSubJsonService("", "", "", "", NewSubService("")).GetJson("s1", "req.example.com", false)
+	if err != nil {
+		t.Fatalf("GetJson: %v", err)
+	}
+	var doc map[string]any
+	if err := json.Unmarshal([]byte(out), &doc); err != nil {
+		t.Fatalf("a single-config subscription should be one JSON object: %v\n%s", err, out)
+	}
+	reality := proxyRealitySettings(t, doc)
+	if got := reality["serverName"]; got != "host.reality.com" {
+		t.Fatalf("json serverName = %v, want the host's SNI host.reality.com", got)
+	}
+	if names, leaked := reality["serverNames"]; leaked {
+		t.Fatalf("server-side serverNames %v leaked into the json reality client:\n%s", names, out)
+	}
+
+	yaml, _, err := NewSubClashService(false, "", NewSubService("")).GetClash("s1", "req.example.com")
+	if err != nil {
+		t.Fatalf("GetClash: %v", err)
+	}
+	if !strings.Contains(yaml, "servername: host.reality.com") {
+		t.Fatalf("clash proxy should carry the host's SNI:\n%s", yaml)
+	}
+}
+
+func proxyRealitySettings(t *testing.T, doc map[string]any) map[string]any {
+	t.Helper()
+	outbounds, _ := doc["outbounds"].([]any)
+	for _, ob := range outbounds {
+		outbound, _ := ob.(map[string]any)
+		if outbound["tag"] != "proxy" {
+			continue
+		}
+		stream, _ := outbound["streamSettings"].(map[string]any)
+		reality, ok := stream["realitySettings"].(map[string]any)
+		if !ok {
+			t.Fatalf("proxy outbound has no realitySettings: %v", outbound)
+		}
+		return reality
+	}
+	t.Fatalf("no proxy outbound in %v", doc)
+	return nil
+}
+
 // #9 — ExcludeFromSubTypes is honored per format: a host excluded from clash is
 // absent from GetClash but present in the raw GetSubs output.
 func TestSub_ExcludeFromSubTypes(t *testing.T) {