mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-04 01:17:15 +00:00
fix(hysteria): standard geco share links and persistent uTLS None (#6325)
- Export standard gecko obfs query params in hysteria2 share links - Enforce packet size bounds across Go and TypeScript link handlers - Persist uTLS None explicitly and initialize new TLS inbounds to chrome - Tear down stackTun safely without closeMu deadlock against WriteNotify Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
This commit is contained in:
+58
-10
@@ -1176,9 +1176,8 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
|
||||
}
|
||||
}
|
||||
|
||||
// salamander obfs (Hysteria2). Emit only the standard URI fields;
|
||||
// the non-standard fm=<json> finalmask dump breaks mihomo and other
|
||||
// Hysteria2 clients that reject unknown query params.
|
||||
// salamander obfs (Hysteria2): standard URI fields only -- an fm=<json>
|
||||
// dump breaks strict clients. packetSize exports as v2rayN's gecko pair.
|
||||
if finalmask, ok := stream["finalmask"].(map[string]any); ok {
|
||||
if udpMasks, ok := finalmask["udp"].([]any); ok {
|
||||
for _, m := range udpMasks {
|
||||
@@ -1188,13 +1187,23 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
|
||||
}
|
||||
settings, _ := mask["settings"].(map[string]any)
|
||||
if pw, ok := settings["password"].(string); ok && pw != "" {
|
||||
if extra := extraSalamanderKeys(settings); len(extra) > 0 {
|
||||
packetSize, _ := settings["packetSize"].(string)
|
||||
gecko := parseHysteriaPacketSize(packetSize)
|
||||
if gecko != "" {
|
||||
params["obfs"] = "gecko"
|
||||
params["minPacketSize"], params["maxPacketSize"] = splitHysteriaPacketSize(gecko)
|
||||
}
|
||||
// packetSize rides its own URI fields; anything else still
|
||||
// breaks standard clients and must warn even when gecko fires.
|
||||
if extra := extraSalamanderKeys(settings, gecko != ""); len(extra) > 0 {
|
||||
warningKey := fmt.Sprintf("%d:%v", inbound.Id, extra)
|
||||
if _, loaded := salamanderWarningSeen.LoadOrStore(warningKey, struct{}{}); !loaded {
|
||||
logger.Warningf("SubService - inbound %d: salamander settings %v cannot be expressed in a hysteria2 URI; standard clients will fail the handshake", inbound.Id, extra)
|
||||
}
|
||||
}
|
||||
params["obfs"] = "salamander"
|
||||
if params["obfs"] == "" {
|
||||
params["obfs"] = "salamander"
|
||||
}
|
||||
params["obfs-password"] = pw
|
||||
break
|
||||
}
|
||||
@@ -1260,6 +1269,44 @@ func hysteriaHopPorts(stream map[string]any) string {
|
||||
return strings.TrimSpace(ports)
|
||||
}
|
||||
|
||||
// gecko packetSize bounds mirror xray-core's salamander buffer cap and the
|
||||
// frontend editor, so both link generators emit identical URIs.
|
||||
const (
|
||||
geckoMinPacketSize = 1
|
||||
geckoMaxPacketSize = 2048
|
||||
)
|
||||
|
||||
// parseHysteriaPacketSize validates an xray-core salamander packetSize range
|
||||
// ("512-1200", the Gecko obfs marker). Returns canonical "min-max" or "".
|
||||
func parseHysteriaPacketSize(value string) string {
|
||||
minStr, maxStr, ok := strings.Cut(value, "-")
|
||||
if !ok || minStr == "" || maxStr == "" {
|
||||
return ""
|
||||
}
|
||||
for _, c := range minStr {
|
||||
if c < '0' || c > '9' {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
for _, c := range maxStr {
|
||||
if c < '0' || c > '9' {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
minVal, err1 := strconv.Atoi(minStr)
|
||||
maxVal, err2 := strconv.Atoi(maxStr)
|
||||
if err1 != nil || err2 != nil ||
|
||||
minVal < geckoMinPacketSize || maxVal < minVal || maxVal > geckoMaxPacketSize {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", minVal, maxVal)
|
||||
}
|
||||
|
||||
func splitHysteriaPacketSize(value string) (string, string) {
|
||||
minStr, maxStr, _ := strings.Cut(value, "-")
|
||||
return minStr, maxStr
|
||||
}
|
||||
|
||||
// loadNodes refreshes nodesByID from the DB. Called once per request so
|
||||
// the per-inbound resolveInboundAddress lookups are pure map reads.
|
||||
// We filter to address != ” so a half-configured node row doesn't
|
||||
@@ -2843,14 +2890,15 @@ func getHostFromXFH(s string) (string, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// extraSalamanderKeys lists salamander settings the hysteria2 URI cannot carry.
|
||||
// A server using them rejects every client built from the emitted link.
|
||||
func extraSalamanderKeys(settings map[string]any) []string {
|
||||
// extraSalamanderKeys lists salamander settings unexpressible in hysteria2 URI;
|
||||
// a server using any reported key rejects clients built from the link.
|
||||
func extraSalamanderKeys(settings map[string]any, expressedPacketSize bool) []string {
|
||||
var extra []string
|
||||
for k := range settings {
|
||||
if k != "password" {
|
||||
extra = append(extra, k)
|
||||
if k == "password" || (k == "packetSize" && expressedPacketSize) {
|
||||
continue
|
||||
}
|
||||
extra = append(extra, k)
|
||||
}
|
||||
sort.Strings(extra)
|
||||
return extra
|
||||
|
||||
Reference in New Issue
Block a user