Files
3x-ui/internal/util/maskcompat/salamander.go
T
MHSanaei 0466e8b0bd fix(finalmask): refuse a salamander password xray-core cannot open
xray-core needs a salamander (and gecko) password of at least 4 bytes
(smPSKMinLen), but checks it only when a socket opens: xray run -test
reports Configuration OK. On an inbound the listener then fails and Xray
does not start at all, taking every other inbound down; on a client the
dial fails. The panel saved any password, and the finalmask editor
starts a new salamander mask with an empty one.

maskcompat.CheckSalamanderPasswords applies the core's byte rule, pinned
against the core's own ListenPacket, and every path that stores a
finalmask refuses a short password: inbound add and update, hosts, the
subscription's global finalmask, and the inbounds and outbounds of the
Xray template. The editor flags it inline as it is typed.
2026-10-11 18:26:45 +02:00

28 lines
943 B
Go

package maskcompat
import (
"fmt"
"strings"
)
// SalamanderMinPSK is xray-core's smPSKMinLen, in bytes; the core enforces it only when a socket
// opens, so a shorter password loads fine and then stops the listener or fails every dial.
const SalamanderMinPSK = 4
// CheckSalamanderPasswords refuses a salamander (or gecko) UDP mask the core would not open.
func CheckSalamanderPasswords(finalmask any) error {
fm, _ := finalmask.(map[string]any)
masks, _ := fm["udp"].([]any)
for _, entry := range masks {
mask, _ := entry.(map[string]any)
if maskType, _ := mask["type"].(string); !strings.EqualFold(maskType, "salamander") {
continue
}
settings, _ := mask["settings"].(map[string]any)
if password, _ := settings["password"].(string); len(password) < SalamanderMinPSK {
return fmt.Errorf("salamander finalmask password must be at least %d bytes; xray-core refuses a shorter one", SalamanderMinPSK)
}
}
return nil
}