Selaa lähdekoodia

fix(sub): wrap JSON-subscription SS/Trojan outbound in servers[] array

The flat top-level address/method/password form only parses on recent
xray-core; older bundled cores (e.g. in v2rayN) reject it. Restore the standard
"servers" array used through 2.9.x so the JSON subscription connects across all
xray-core versions. VMess/VLESS keep the flat vnext fallback, which is long
established in xray-core.
MHSanaei 1 päivä sitten
vanhempi
sitoutus
340d0df9fc
2 muutettua tiedostoa jossa 27 lisäystä ja 10 poistoa
  1. 9 3
      internal/sub/json_service.go
  2. 18 7
      internal/sub/json_service_test.go

+ 9 - 3
internal/sub/json_service.go

@@ -425,16 +425,22 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
 	}
 	outbound.StreamSettings = streamSettings
 
-	settings := map[string]any{
+	// Wrap the endpoint in a "servers" array (the standard Xray schema for
+	// Shadowsocks/Trojan outbounds). The flat top-level form only parses on very
+	// recent xray-core; older bundled cores (e.g. in v2rayN) reject it, so SS
+	// links fail to connect. See genVnext/genVless for the VMess/VLESS shape.
+	server := map[string]any{
 		"address":  serverData[0].Address,
 		"port":     serverData[0].Port,
 		"password": serverData[0].Password,
 		"level":    8,
 	}
 	if inbound.Protocol == model.Shadowsocks {
-		settings["method"] = serverData[0].Method
+		server["method"] = serverData[0].Method
+	}
+	outbound.Settings = map[string]any{
+		"servers": []any{server},
 	}
-	outbound.Settings = settings
 
 	result, _ := json.MarshalIndent(outbound, "", "  ")
 	return result

+ 18 - 7
internal/sub/json_service_test.go

@@ -128,21 +128,32 @@ func TestSubJsonServiceVmessFlattened(t *testing.T) {
 	}
 }
 
-func TestSubJsonServiceServerFlattened(t *testing.T) {
+// Shadowsocks/Trojan outbounds must use the standard "servers" array so older
+// bundled xray-cores (e.g. v2rayN) parse them; the flat top-level form only
+// works on very recent xray-core.
+func TestSubJsonServiceServerUsesServersArray(t *testing.T) {
 	trojan := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.Trojan, Settings: `{}`}
 	client := model.Client{Password: "p4ss"}
 
 	settings := outboundSettings(t, NewSubJsonService("", "", "", nil).genServer(trojan, nil, client, ""))
-	if _, ok := settings["servers"]; ok {
-		t.Fatal("trojan outbound must not use servers array")
+	server := firstServer(settings)
+	if server == nil {
+		t.Fatalf("trojan outbound must use a servers array, got: %#v", settings)
 	}
-	if settings["password"] != "p4ss" || settings["address"] != "1.2.3.4" {
-		t.Fatalf("flat trojan settings wrong: %#v", settings)
+	if server["password"] != "p4ss" || server["address"] != "1.2.3.4" {
+		t.Fatalf("trojan server entry wrong: %#v", server)
+	}
+	if _, ok := server["method"]; ok {
+		t.Fatalf("trojan must not carry method: %#v", server)
 	}
 
 	ss := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.Shadowsocks, Settings: `{"method":"aes-256-gcm"}`}
 	ssSettings := outboundSettings(t, NewSubJsonService("", "", "", nil).genServer(ss, nil, client, ""))
-	if ssSettings["method"] != "aes-256-gcm" {
-		t.Fatalf("flat shadowsocks must carry method: %#v", ssSettings)
+	ssServer := firstServer(ssSettings)
+	if ssServer == nil {
+		t.Fatalf("shadowsocks outbound must use a servers array, got: %#v", ssSettings)
+	}
+	if ssServer["method"] != "aes-256-gcm" {
+		t.Fatalf("shadowsocks server entry must carry method: %#v", ssServer)
 	}
 }