Jelajahi Sumber

fix(sub): use configured spiderX instead of always randomizing

applyShareRealityParams and SubJsonService.realityData generated a fresh
random spx on every export, so share links, "export all links", and JSON
subscriptions never matched a spiderX configured on the inbound and two
exports of the same client disagreed with each other. Read the value from
realitySettings.settings like pbk/fp/pqv and keep the random value only as
a fallback when none is configured.

Closes #5718
MHSanaei 1 hari lalu
induk
melakukan
1f2e3e1447

+ 3 - 1
internal/sub/json_service.go

@@ -331,8 +331,10 @@ func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
 	rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
 	rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
 
-	// Set random data
 	rltyData["spiderX"] = "/" + random.Seq(15)
+	if spx, ok := rltyClientSettings["spiderX"].(string); ok && spx != "" {
+		rltyData["spiderX"] = spx
+	}
 	shortIds, ok := rData["shortIds"].([]any)
 	if ok && len(shortIds) > 0 {
 		rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)

+ 43 - 0
internal/sub/json_service_test.go

@@ -254,3 +254,46 @@ func TestSubJsonServiceGlobalMuxWhenNoXmux(t *testing.T) {
 		t.Fatalf("mux payload wrong: %#v", m)
 	}
 }
+
+func TestSubJsonServiceRealityDataUsesConfiguredSpiderX(t *testing.T) {
+	svc := NewSubJsonService("", "", "", nil)
+
+	stream := svc.streamData(`{
+		"network":"tcp","security":"reality","tcpSettings":{"header":{"type":"none"}},
+		"realitySettings":{
+			"serverNames":["reality.example.com"],
+			"shortIds":["ab12cd"],
+			"settings":{"publicKey":"PBKvalue","fingerprint":"firefox","spiderX":"/mypath"}
+		}
+	}`)
+
+	rlty, _ := stream["realitySettings"].(map[string]any)
+	if rlty == nil {
+		t.Fatal("streamData dropped realitySettings")
+	}
+	if rlty["spiderX"] != "/mypath" {
+		t.Fatalf("spiderX = %v, want configured /mypath (#5718)", rlty["spiderX"])
+	}
+}
+
+func TestSubJsonServiceRealityDataSpiderXFallsBackToRandom(t *testing.T) {
+	svc := NewSubJsonService("", "", "", nil)
+
+	stream := svc.streamData(`{
+		"network":"tcp","security":"reality","tcpSettings":{"header":{"type":"none"}},
+		"realitySettings":{
+			"serverNames":["reality.example.com"],
+			"shortIds":["ab12cd"],
+			"settings":{"publicKey":"PBKvalue","fingerprint":"firefox"}
+		}
+	}`)
+
+	rlty, _ := stream["realitySettings"].(map[string]any)
+	if rlty == nil {
+		t.Fatal("streamData dropped realitySettings")
+	}
+	spx, _ := rlty["spiderX"].(string)
+	if len(spx) != 16 || spx[0] != '/' {
+		t.Fatalf("spiderX fallback = %q, want random 16-char /-prefixed value", spx)
+	}
+}

+ 5 - 0
internal/sub/service.go

@@ -1357,6 +1357,11 @@ func applyShareRealityParams(stream map[string]any, params map[string]string) {
 			}
 		}
 		params["spx"] = "/" + random.Seq(15)
+		if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
+			if spx, ok := spxValue.(string); ok && len(spx) > 0 {
+				params["spx"] = spx
+			}
+		}
 	}
 }
 

+ 24 - 6
internal/sub/service_sharelink_test.go

@@ -52,10 +52,8 @@ func TestGenVlessLink_TLSParamsMapped(t *testing.T) {
 	}
 }
 
-// TestGenVlessLink_RealityParamsMapped locks the reality field mapping
-// (applyShareRealityParams, service.go:1147). serverNames/shortIds are single-element
-// so random.Num is deterministic (index 0); spx is random so it is asserted by prefix.
-// Distinct pbk/sid values catch a pbk<->sid swap mutant.
+// Locks the reality field mapping of applyShareRealityParams; a configured
+// spiderX must round-trip verbatim (#5718), distinct pbk/sid catch a swap mutant.
 func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
 	stream := `{
 		"network":"tcp","security":"reality",
@@ -63,7 +61,7 @@ func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
 		"realitySettings":{
 			"serverNames":["reality.example.com"],
 			"shortIds":["ab12cd"],
-			"settings":{"publicKey":"PBKvalue","fingerprint":"firefox"}
+			"settings":{"publicKey":"PBKvalue","fingerprint":"firefox","spiderX":"/mypath"}
 		}
 	}`
 	s := &SubService{}
@@ -75,7 +73,7 @@ func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
 		"pbk=PBKvalue",
 		"sid=ab12cd",
 		"fp=firefox",
-		"spx=%2F", // "/" + random.Seq(15), percent-encoded leading slash
+		"spx=%2Fmypath",
 	}
 	for _, w := range wants {
 		if !strings.Contains(link, w) {
@@ -87,3 +85,23 @@ func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
 		t.Fatalf("reality pbk/sid mapping crossed: %s", link)
 	}
 }
+
+// Without a configured spiderX, spx must still fall back to a random
+// "/"-prefixed value so clients always receive a plausible path.
+func TestGenVlessLink_RealitySpiderXFallsBackToRandom(t *testing.T) {
+	stream := `{
+		"network":"tcp","security":"reality",
+		"tcpSettings":{"header":{"type":"none"}},
+		"realitySettings":{
+			"serverNames":["reality.example.com"],
+			"shortIds":["ab12cd"],
+			"settings":{"publicKey":"PBKvalue","fingerprint":"firefox"}
+		}
+	}`
+	s := &SubService{}
+	link := s.genVlessLink(shareLinkInbound(stream), "user")
+
+	if !strings.Contains(link, "spx=%2F") {
+		t.Fatalf("reality link missing random spx fallback\n got: %s", link)
+	}
+}