Files
3x-ui/internal/tuic/server_settings_test.go
T
MHSanaei 5cf2ab4f69 fix(tuic): bound the timeout settings at a day
max_idle_time and authentication_timeout are second counts multiplied
into a Duration with no bound, so a stored value past the int64 range
wrapped negative and the QUIC listener panicked on the first
ClientHello, taking the panel down at every start until the row was
fixed. Saves now refuse more than a day, and a stored row past it fails
its own listener start instead.
2026-10-31 14:53:20 +01:00

146 lines
5.1 KiB
Go

package tuic
import (
"fmt"
"slices"
"strings"
"testing"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
)
func TestNormalizeCongestionControl(t *testing.T) {
tests := []struct {
name string
input string
want string
wantValid bool
}{
{name: "default", want: "bbr", wantValid: true},
{name: "bbr", input: "bbr", want: "bbr", wantValid: true},
{name: "cubic", input: "cubic", want: "cubic", wantValid: true},
{name: "new reno", input: "new_reno", want: "new_reno", wantValid: true},
{name: "reno alias", input: "reno", want: "new_reno", wantValid: true},
{name: "unknown falls back to reno", input: "quic", want: "new_reno"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, valid := normalizeCongestionControl(test.input)
if got != test.want || valid != test.wantValid {
t.Fatalf("normalizeCongestionControl(%q) = (%q, %t), want (%q, %t)", test.input, got, valid, test.want, test.wantValid)
}
})
}
}
func TestLogfUsesCommonLoggerAndHonorsThreshold(t *testing.T) {
tests := []struct {
level string
want []string
}{
{level: "debug", want: []string{"debug", "info", "warn", "error"}},
{level: "info", want: []string{"info", "warn", "error"}},
{level: "warn", want: []string{"warn", "error"}},
{level: "error", want: []string{"error"}},
}
for _, test := range tests {
t.Run(test.level, func(t *testing.T) {
marker := fmt.Sprintf("tuic-log-%s-%d", test.level, time.Now().UnixNano())
server := &Server{id: 99001}
server.updateRuntimeSettings("log-test", "bbr", test.level)
for _, level := range []struct {
name string
value uint32
}{{"debug", tuicLogDebug}, {"info", tuicLogInfo}, {"warn", tuicLogWarn}, {"error", tuicLogError}} {
server.logf(level.value, "%s-%s", marker, level.name)
}
logs := strings.Join(logger.GetLogs(10000, "DEBUG"), "\n")
for _, name := range []string{"debug", "info", "warn", "error"} {
want := slices.Contains(test.want, name)
got := strings.Contains(logs, marker+"-"+name)
if got != want {
t.Errorf("log level %s present = %t, want %t", name, got, want)
}
}
if !strings.Contains(logs, "inbound 99001 (log-test)") {
t.Fatal("TUIC event was not written through the shared 3x-ui logger")
}
})
}
}
func TestLogLevelsAreIsolatedPerInboundAndUpdateLive(t *testing.T) {
marker := fmt.Sprintf("tuic-log-isolation-%d", time.Now().UnixNano())
debugInbound := &Server{id: 99011}
errorInbound := &Server{id: 99012}
debugInbound.updateRuntimeSettings("debug-inbound", "bbr", "debug")
errorInbound.updateRuntimeSettings("error-inbound", "bbr", "error")
debugInbound.logf(tuicLogInfo, "%s-debug", marker)
errorInbound.logf(tuicLogInfo, "%s-hidden", marker)
debugInbound.UpdateRuntimeSettings("debug-inbound", "bbr", "warn")
debugInbound.logf(tuicLogInfo, "%s-hidden-after-update", marker)
debugInbound.logf(tuicLogWarn, "%s-warn-after-update", marker)
logs := strings.Join(logger.GetLogs(10000, "DEBUG"), "\n")
if !strings.Contains(logs, marker+"-debug") || !strings.Contains(logs, marker+"-warn-after-update") {
t.Fatal("expected permitted events from debug inbound")
}
if strings.Contains(logs, marker+"-hidden") || strings.Contains(logs, marker+"-hidden-after-update") {
t.Fatal("a TUIC inbound emitted an event below its own log threshold")
}
}
func TestEnsureStartupFailureHonorsInboundLogThreshold(t *testing.T) {
tests := []struct {
level string
want bool
}{
{level: "error", want: false},
{level: "warn", want: true},
}
for _, test := range tests {
t.Run(test.level, func(t *testing.T) {
marker := fmt.Sprintf("tuic-start-failure-%s-%d", test.level, time.Now().UnixNano())
manager := &Manager{servers: map[int]*managed{}, lastStartErr: map[int]string{}, pendingTraffic: map[string]ClientTrafficDelta{}}
err := manager.Ensure(Instance{
Id: 99031,
Tag: marker,
Listen: "127.0.0.1",
Port: 0,
LogLevel: test.level,
CongestionControl: "bbr",
Clients: []TuicClientSettings{{UUID: "a0000000-0000-0000-0000-000000000031", Password: "p", Email: "startup@x"}},
})
if err == nil {
t.Fatal("Ensure unexpectedly started without a certificate")
}
logs := strings.Join(logger.GetLogs(10000, "DEBUG"), "\n")
got := strings.Contains(logs, marker+"): failed to start server")
if got != test.want {
t.Fatalf("startup warning logged = %t, want %t", got, test.want)
}
})
}
}
// Seconds turn into a Duration by multiplication; a pushed value past the int64 range
// wrapped negative and the QUIC listener panicked on the first ClientHello, every start.
func TestNewServerRefusesTimeoutsPastADay(t *testing.T) {
cert, key := generateTestCert(t)
for name, inst := range map[string]Instance{
"max_idle_time": {MaxIdleTime: 1 << 40},
"authentication_timeout": {AuthenticationTimeout: MaxTimeoutSeconds + 1},
} {
inst.Listen, inst.Certificate, inst.PrivateKey = "127.0.0.1", string(cert), string(key)
if s, err := NewServer(inst, &SocksRelay{Addr: "127.0.0.1:1"}); err == nil {
_ = s.Close()
t.Errorf("%s past a day was accepted", name)
}
}
}