浏览代码

fix(sub): append host serverDescription to hysteria links (#6740)

* fix(sub): append host serverDescription to hysteria links

A host's Description reached vless/trojan/ss through buildEndpointLinks, but
genHysteriaLink renders the fragment in its own externalProxy loop and never
added the suffix, so Happ fell back to its "Hysteria | hysteria | TLS" caption
for every Hysteria server on a host that also serves VLESS (#6738).

Reuse appendHappServerDescription with the description the endpoint map already
carries, so no key lookup is duplicated and a host with no description emits the
same bytes as before. genTuicLink (service.go:912) has the same gap; left alone
to keep this diff to the reported protocol.

Regression test is red without the fix for both hysteria:// and hysteria2://.

* chore(sub): trim the hysteria serverDescription regression test

The no-description test passed with and without the #6738 fix: the empty
description branch is already pinned by TestAppendHappServerDescription, so
it certified nothing about this change. Also cut the remaining test's comment
block to the two-line limit CLAUDE.md sets.

---------

Co-authored-by: MHSanaei <[email protected]>
冰 1 天之前
父节点
当前提交
e897b0957a
共有 2 个文件被更改,包括 62 次插入 和 1 次删除
  1. 57 0
      internal/sub/hysteria_server_description_test.go
  2. 5 1
      internal/sub/service.go

+ 57 - 0
internal/sub/hysteria_server_description_test.go

@@ -0,0 +1,57 @@
+package sub
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+
+	"github.com/mhsanaei/3x-ui/v3/internal/database/model"
+)
+
+// #6738: without the host's Description on hysteria(2):// links, Happ falls back
+// to its own "Hysteria | hysteria | TLS" caption on a host that also serves VLESS.
+func TestGenHysteriaLinkAppendsHostServerDescription(t *testing.T) {
+	tests := map[string]struct {
+		version int
+		scheme  string
+	}{
+		"hysteria v1": {version: 1, scheme: "hysteria://"},
+		"hysteria v2": {version: 2, scheme: "hysteria2://"},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			host := &model.Host{
+				Address: "hy.example.com", Port: 443,
+				Remark: "Poland", ServerDescription: "Wi-Fi",
+			}
+			stream := map[string]any{
+				"security":      "tls",
+				"externalProxy": []any{hostToExternalProxyMap(host, "hy.example.com", 443)},
+			}
+			rawStream, err := json.Marshal(stream)
+			if err != nil {
+				t.Fatalf("marshal stream settings: %v", err)
+			}
+			// The inbound's own `version` picks the hysteria vs hysteria2 scheme.
+			rawSettings, err := json.Marshal(map[string]any{
+				"version": tc.version,
+				"clients": []any{map[string]any{"auth": "secret", "email": "user"}},
+			})
+			if err != nil {
+				t.Fatalf("marshal inbound settings: %v", err)
+			}
+			in := &model.Inbound{
+				Id: 920010, Listen: "203.0.113.1", Port: 443, Protocol: model.Hysteria,
+				Remark: "hy", StreamSettings: string(rawStream), Settings: string(rawSettings),
+			}
+			got := (&SubService{}).genHysteriaLink(in, "user")
+			if !strings.HasPrefix(got, tc.scheme) {
+				t.Fatalf("link scheme changed.\n got: %s\nwant prefix: %s", got, tc.scheme)
+			}
+			// base64("Wi-Fi"), matching the reporter's subscription output.
+			if !strings.HasSuffix(got, "?serverDescription=V2ktRmk=") {
+				t.Fatalf("host serverDescription missing from fragment.\n got: %s\nwant suffix: ?serverDescription=V2ktRmk=", got)
+			}
+		})
+	}
+}

+ 5 - 1
internal/sub/service.go

@@ -1535,7 +1535,11 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
 			applyExternalProxyHysteriaParams(ep, epParams)
 
 			link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(dest, int(portF)))
-			links = append(links, buildLinkWithParams(link, epParams, s.endpointRemark(inbound, email, ep, "quic")))
+			// VLESS/Trojan/SS get the host's description through buildEndpointLinks;
+			// this loop renders the fragment itself, so add it here too (#6738).
+			remark := s.endpointRemark(inbound, email, ep, "quic")
+			remark = appendHappServerDescription(remark, externalProxyToEndpoint(ep).ServerDescription)
+			links = append(links, buildLinkWithParams(link, epParams, remark))
 		}
 		return strings.Join(links, "\n")
 	}