From e897b0957a12c3a106f505e0551c18887b0528d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0?= <77561975+AyaseEli-Bing@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:05:13 +0800 Subject: [PATCH] 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 --- .../sub/hysteria_server_description_test.go | 57 +++++++++++++++++++ internal/sub/service.go | 6 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 internal/sub/hysteria_server_description_test.go diff --git a/internal/sub/hysteria_server_description_test.go b/internal/sub/hysteria_server_description_test.go new file mode 100644 index 000000000..2dd7c440e --- /dev/null +++ b/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) + } + }) + } +} diff --git a/internal/sub/service.go b/internal/sub/service.go index a407a50c8..ec5a51818 100644 --- a/internal/sub/service.go +++ b/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") }