fix(sub): keep Hysteria2 mport on external-proxy links

genHysteriaLink only looked up the UDP hop range on the no-endpoint path,
after the externalProxy fan-out had already returned. An inbound with Hosts
therefore emitted per-host links without mport, so clients pinned themselves
to the single listening port and silently lost port hopping. Set the param
before the fan-out so every endpoint inherits it, matching the frontend
link builder and the Clash emitter.

Closes #6264
This commit is contained in:
Sanaei
2026-08-24 13:27:39 +02:00
parent f13baa9af5
commit 7a595cb46d
2 changed files with 36 additions and 3 deletions
+6 -3
View File
@@ -1209,6 +1209,12 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
protocol = "hysteria"
}
// Set before the externalProxy fan-out: a Host overrides only the
// address, so every endpoint inherits the inbound's UDP hop range.
if hopPorts := hysteriaHopPorts(stream); hopPorts != "" {
params["mport"] = hopPorts
}
// Fan out one link per External Proxy entry if any. Previously this
// generator ignored `externalProxy` entirely, so the link kept the
// server's own IP/port even when the admin configured an alternate
@@ -1238,9 +1244,6 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
// No external proxy configured — use the inbound's resolved address so
// node-managed inbounds get the node's host instead of the central panel's.
if hopPorts := hysteriaHopPorts(stream); hopPorts != "" {
params["mport"] = hopPorts
}
link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", "quic"))
}
+30
View File
@@ -1189,3 +1189,33 @@ func TestGenHysteriaLinkOmitsFinalMaskQueryParam(t *testing.T) {
t.Fatalf("missing standard obfs-password: %s", got)
}
}
func TestGenHysteriaLinkKeepsHopPortsWithExternalProxy(t *testing.T) {
stream := `{
"security":"tls",
"tlsSettings":{"serverName":"hy.sni"},
"finalmask":{"quicParams":{"udpHop":{"ports":"20000-50000","interval":"5-10"}}},
"externalProxy":[
{"dest":"cdn.example.com","port":8443},
{"dest":"2001:db8::10","port":9443}
]
}`
in := &model.Inbound{
Listen: "203.0.113.1",
Port: 443,
Protocol: model.Hysteria,
Remark: "hy2",
Settings: `{"version":2,"clients":[{"auth":"hyauth","email":"user"}]}`,
StreamSettings: stream,
}
got := (&SubService{}).genHysteriaLink(in, "user")
links := strings.Split(got, "\n")
if len(links) != 2 {
t.Fatalf("expected one link per external proxy, got %d: %q", len(links), got)
}
for _, link := range links {
if !strings.Contains(link, "mport=20000-50000") {
t.Fatalf("external-proxy link lost the UDP hop range: %s", link)
}
}
}