fix(sub): export Hysteria2 Gecko obfs in Clash/Mihomo subscriptions

A salamander finalmask carrying a packetSize range is Gecko obfs. The
hysteria2:// link generators already export it, but buildHysteriaProxy
copied only the password, so Clash/Mihomo received plain salamander and
could never complete the handshake against a Gecko server.

Emit mihomo's gecko form instead: obfs: gecko plus integer
obfs-min-packet-size / obfs-max-packet-size, validated by the same
parseHysteriaPacketSize bounds the URI export uses. A password-only mask
keeps the plain salamander output.

Closes #6822
This commit is contained in:
MHSanaei
2026-10-14 14:42:00 +02:00
parent db8b01b8ed
commit cb4f2ec92f
2 changed files with 50 additions and 2 deletions
+10 -2
View File
@@ -7,6 +7,7 @@ import (
"maps"
"net/netip"
"slices"
"strconv"
"strings"
"github.com/goccy/go-json"
@@ -540,8 +541,8 @@ func (s *SubClashService) buildHysteriaProxy(subReq *SubService, inbound *model.
proxy["fingerprint"] = certFingerprint
}
// Salamander obfs (Hysteria2). Read the same finalmask.udp[salamander]
// block the subscription link generator uses.
// Salamander obfs (Hysteria2), read from the same finalmask.udp[salamander]
// block as the link generator; a packetSize range is mihomo's gecko obfs.
if finalmask, ok := rawStream["finalmask"].(map[string]any); ok {
if udpMasks, ok := finalmask["udp"].([]any); ok {
for _, m := range udpMasks {
@@ -553,6 +554,13 @@ func (s *SubClashService) buildHysteriaProxy(subReq *SubService, inbound *model.
if pw, ok := settings["password"].(string); ok && pw != "" {
proxy["obfs"] = "salamander"
proxy["obfs-password"] = pw
packetSize, _ := settings["packetSize"].(string)
if gecko := parseHysteriaPacketSize(packetSize); gecko != "" {
minStr, maxStr := splitHysteriaPacketSize(gecko)
proxy["obfs"] = "gecko"
proxy["obfs-min-packet-size"], _ = strconv.Atoi(minStr)
proxy["obfs-max-packet-size"], _ = strconv.Atoi(maxStr)
}
break
}
}