fix(sub): carry a host's Final Mask into raw share links

A Host's Final Mask was merged into the JSON and Clash subscription
outputs via applyHostStreamOverrides, but the raw link builders compute
the fm param once from the inbound's own streamSettings.finalmask
before the per-host fan-out, and the endpoint override path never read
the host's mask. A Final Mask configured only on a host was silently
dropped from vless/trojan/ss/vmess share links while an inbound-level
mask worked everywhere.

Merge the host mask into the fm param per endpoint with the same
additive semantics as the JSON path (host tcp/udp masks appended to the
inbound's, quicParams only when the inbound has none), for both the
URL-param and the VMess object link forms.

Closes #5831
This commit is contained in:
MHSanaei
2026-07-07 12:44:18 +02:00
parent 52d4af71bc
commit cc3303dd8c
4 changed files with 107 additions and 0 deletions
+23
View File
@@ -2,6 +2,7 @@ package sub
import (
"fmt"
"net/url"
"path/filepath"
"strings"
"testing"
@@ -270,6 +271,28 @@ func TestSub_HostAllowInsecure(t *testing.T) {
}
}
// A host's Final Mask reaches the raw share link as the fm param, merged with
// any inbound-level mask (#5831).
func TestSub_HostFinalMask_RawLink(t *testing.T) {
seedSubDB(t)
ib := seedSubInbound(t, "s1", "fmh", 4455, 1,
`{"network":"tcp","security":"tls","tlsSettings":{"serverName":"base.sni"},"finalmask":{"tcp":[{"type":"sudoku"}]}}`)
seedHost(t, &model.Host{
InboundId: ib.Id, SortOrder: 0, Remark: "FM", Address: "fm.cdn.com", Port: 8443, Security: "tls",
FinalMask: `{"tcp":[{"type":"fragment"}]}`,
})
links, _, _, _, err := NewSubService("").GetSubs("s1", "req.example.com")
if err != nil {
t.Fatalf("GetSubs: %v", err)
}
joined := strings.Join(links, "\n")
wantFm := "fm=" + url.QueryEscape(`{"tcp":[{"type":"sudoku"},{"type":"fragment"}]}`)
if !strings.Contains(joined, wantFm) {
t.Fatalf("raw link should merge the host Final Mask into fm.\n got: %s\nwant substring: %s", joined, wantFm)
}
}
// A host's sockoptParams is injected into the JSON output stream (sockopt is
// stripped from the base stream, re-added per host).
func TestSub_HostSockoptJSON(t *testing.T) {