Ver Fonte

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
Sanaei há 11 horas atrás
pai
commit
7a595cb46d
2 ficheiros alterados com 36 adições e 3 exclusões
  1. 6 3
      internal/sub/service.go
  2. 30 0
      internal/sub/service_test.go

+ 6 - 3
internal/sub/service.go

@@ -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 - 0
internal/sub/service_test.go

@@ -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)
+		}
+	}
+}