Files
3x-ui/internal/web/service/inbound_finalmask_salamander_test.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

84 lines
3.3 KiB
Go

package service
import (
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
)
func hysteriaSalamanderStream(password string) string {
return `{"network":"hysteria","security":"tls","hysteriaSettings":{"version":2},
"tlsSettings":{"alpn":["h3"],"certificates":[{"certificateFile":"/etc/ssl/h.crt","keyFile":"/etc/ssl/h.key"}]},
"finalmask":{"udp":[{"type":"salamander","settings":{"password":"` + password + `"}}]}}`
}
func hysteriaInbound(port int, stream string) *model.Inbound {
return &model.Inbound{
Enable: true, Listen: "127.0.0.1", Port: port, Protocol: model.Hysteria,
Settings: `{"version":2,"clients":[{"email":"sal@example.test","auth":"a","enable":true}]}`,
StreamSettings: stream,
}
}
// xray-core loads a salamander mask whose password is under 4 bytes and refuses it only when the
// socket opens: on an inbound that stops every listener, so each save path must refuse it.
func TestSavePathsRefuseShortSalamanderPassword(t *testing.T) {
const want = "salamander finalmask password must be at least 4 bytes"
tests := []struct {
name string
save func(t *testing.T) error
}{
{"add inbound", func(t *testing.T) error {
_, _, err := (&InboundService{}).AddInbound(hysteriaInbound(18443, hysteriaSalamanderStream("")))
return err
}},
{"update inbound", func(t *testing.T) error {
in, _, err := (&InboundService{}).AddInbound(hysteriaInbound(18444, hysteriaSalamanderStream("long-enough")))
if err != nil {
t.Fatalf("AddInbound: %v", err)
}
in.StreamSettings = hysteriaSalamanderStream("abc")
_, _, err = (&InboundService{}).UpdateInbound(in)
return err
}},
{"add host", func(t *testing.T) error {
ib := mkInbound(t, 18445, model.VLESS, `{"clients":[]}`)
_, err := (&HostService{}).AddHostGroup(&entity.HostGroup{
InboundIds: []int{ib.Id}, Remark: "h", Port: 443,
FinalMask: `{"udp":[{"type":"salamander","settings":{"password":"€"}}]}`,
})
return err
}},
{"subscription JSON finalmask", func(t *testing.T) error {
s := &SettingService{}
all, err := s.GetAllSetting()
if err != nil {
t.Fatalf("GetAllSetting: %v", err)
}
all.SubJsonFinalMask = `{"udp":[{"type":"salamander","settings":{"password":"ab"}}]}`
return s.UpdateAllSetting(all, SecretClears{})
}},
{"xray template inbound", func(t *testing.T) error {
return (&XraySettingService{}).SaveXraySetting(`{"inbounds":[{"tag":"hy","port":18446,"protocol":"hysteria",
"settings":{"version":2,"clients":[]},"streamSettings":` + hysteriaSalamanderStream("") + `}],
"outbounds":[{"protocol":"freedom","tag":"direct"}]}`)
}},
{"xray template outbound", func(t *testing.T) error {
return (&XraySettingService{}).SaveXraySetting(`{"outbounds":[{"protocol":"freedom","tag":"direct"},
{"protocol":"hysteria","tag":"hy-out","settings":{"version":2,"address":"hy.example.com","port":443},
"streamSettings":{"network":"hysteria","security":"tls","hysteriaSettings":{"version":2,"auth":"a"},
"finalmask":{"udp":[{"type":"salamander","settings":{"password":"abc"}}]}}}]}`)
}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
setupBulkDB(t)
if err := tc.save(t); err == nil || !strings.Contains(err.Error(), want) {
t.Fatalf("save err = %v, want one containing %q", err, want)
}
})
}
}