| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- package service
- import (
- "encoding/json"
- "fmt"
- "strings"
- "testing"
- "github.com/mhsanaei/3x-ui/v3/internal/database"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- "github.com/mhsanaei/3x-ui/v3/internal/tuic"
- "github.com/mhsanaei/3x-ui/v3/internal/xray"
- )
- func TestInjectTuicSocks(t *testing.T) {
- cfg := &xray.Config{}
- inbounds := []*model.Inbound{
- {
- Id: 5,
- Tag: "tuic-in-5",
- Protocol: model.TUIC,
- Enable: true,
- Settings: `{
- "certificate": "dummy-cert",
- "private_key": "dummy-key",
- "clients": [
- {"uuid": "a0000000-0000-0000-0000-000000000001", "password": "pass1", "email": "[email protected]", "enable": true}
- ]
- }`,
- },
- }
- injectTuicSocks(cfg, inbounds)
- if len(cfg.InboundConfigs) != 1 {
- t.Fatalf("expected 1 injected SOCKS inbound, got %d", len(cfg.InboundConfigs))
- }
- sc := cfg.InboundConfigs[0]
- if sc.Tag != "tuic-in-5" {
- t.Fatalf("expected tag tuic-in-5, got %s", sc.Tag)
- }
- if sc.Protocol != "socks" {
- t.Fatalf("expected protocol socks, got %s", sc.Protocol)
- }
- expectedPort := tuic.SOCKSPortForInbound(5)
- if sc.Port != expectedPort {
- t.Fatalf("expected port %d, got %d", expectedPort, sc.Port)
- }
- if string(sc.Listen) != `"127.0.0.1"` {
- t.Fatalf("expected listen 127.0.0.1, got %s", sc.Listen)
- }
- if string(sc.Sniffing) != tuicEgressSniffingSettings {
- t.Fatalf("expected sniffing settings %s, got %s", tuicEgressSniffingSettings, sc.Sniffing)
- }
- var parsedSettings struct {
- Auth string `json:"auth"`
- UDP bool `json:"udp"`
- }
- if err := json.Unmarshal(sc.Settings, &parsedSettings); err != nil {
- t.Fatalf("failed to unmarshal settings: %v", err)
- }
- if parsedSettings.Auth != "noauth" || !parsedSettings.UDP {
- t.Fatalf("expected auth=noauth, udp=true, got %+v", parsedSettings)
- }
- }
- func TestCheckTuicSocksConflict(t *testing.T) {
- setupConflictDB(t)
- // Seed TUIC inbound with ID 10
- tuicIb := &model.Inbound{
- Id: 10,
- Tag: "tuic-10",
- Protocol: model.TUIC,
- Enable: true,
- Listen: "0.0.0.0",
- Port: 8443,
- Settings: `{"clients":[{"uuid":"a0000000-0000-0000-0000-000000000001","password":"p","email":"[email protected]"}]}`,
- }
- if err := database.GetDB().Create(tuicIb).Error; err != nil {
- t.Fatalf("failed to seed TUIC inbound: %v", err)
- }
- relayPort := tuic.SOCKSPortForInbound(10)
- // Try to create a new TCP inbound on that relayPort on 127.0.0.1
- newIb := &model.Inbound{
- Tag: "colliding-inbound",
- Protocol: model.Mixed,
- Enable: true,
- Listen: "127.0.0.1",
- Port: relayPort,
- }
- detail, err := checkTuicSocksConflict(database.GetDB(), newIb, 0, transportTCP)
- if err != nil {
- t.Fatalf("checkTuicSocksConflict error: %v", err)
- }
- if detail == nil {
- t.Fatalf("expected conflict on port %d, got none", relayPort)
- }
- if detail.Tag != "tuic-10" {
- t.Fatalf("expected conflict tag tuic-10, got %s", detail.Tag)
- }
- }
- func TestCheckTuicSocksReverseConflict(t *testing.T) {
- setupConflictDB(t)
- targetPort := tuic.SOCKSPortForInbound(20)
- // Seed existing inbound on targetPort on 127.0.0.1
- existing := &model.Inbound{
- Id: 99,
- Tag: "existing-on-relay-port",
- Protocol: model.Mixed,
- Enable: true,
- Listen: "127.0.0.1",
- Port: targetPort,
- }
- if err := database.GetDB().Create(existing).Error; err != nil {
- t.Fatalf("failed to seed existing inbound: %v", err)
- }
- detail, err := checkTuicSocksReverseConflict(database.GetDB(), 20)
- if err != nil {
- t.Fatalf("checkTuicSocksReverseConflict error: %v", err)
- }
- if detail == nil {
- t.Fatalf("expected reverse conflict for id 20 on port %d, got none", targetPort)
- }
- if detail.Tag != "existing-on-relay-port" {
- t.Fatalf("expected tag existing-on-relay-port, got %s", detail.Tag)
- }
- }
- func TestDesiredTuicInstances(t *testing.T) {
- setupConflictDB(t)
- ib := &model.Inbound{
- Id: 30,
- Tag: "tuic-desired-test",
- Protocol: model.TUIC,
- Enable: true,
- Listen: "0.0.0.0",
- Port: 9443,
- Settings: `{
- "certificate": "cert",
- "private_key": "key",
- "clients": [
- {"uuid": "a0000000-0000-0000-0000-000000000001", "password": "p1", "email": "[email protected]", "enable": true},
- {"uuid": "a0000000-0000-0000-0000-000000000002", "password": "p2", "email": "[email protected]", "enable": true}
- ]
- }`,
- }
- if err := database.GetDB().Create(ib).Error; err != nil {
- t.Fatalf("failed to seed inbound: %v", err)
- }
- // Add client traffic entry disabling [email protected]
- ct := &xray.ClientTraffic{
- InboundId: 30,
- Email: "[email protected]",
- Enable: false,
- }
- if err := database.GetDB().Create(ct).Error; err != nil {
- t.Fatalf("failed to seed client traffic: %v", err)
- }
- svc := &InboundService{}
- instances, err := svc.DesiredTuicInstances()
- if err != nil {
- t.Fatalf("DesiredTuicInstances failed: %v", err)
- }
- found := false
- for _, inst := range instances {
- if inst.Id == 30 {
- found = true
- if len(inst.Clients) != 1 || inst.Clients[0].Email != "[email protected]" {
- t.Fatalf("expected only [email protected], got %+v", inst.Clients)
- }
- }
- }
- if !found {
- t.Fatal("expected to find instance for inbound 30")
- }
- }
- func TestCheckForwardedPortsConflict_CollidesWithTuicSocksPort(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "tuic-1", "0.0.0.0", 8443, model.TUIC, ``, `{"clients":[{"uuid":"u","password":"p","email":"e"}]}`)
- var tuicInbound model.Inbound
- if err := database.GetDB().Where("tag = ?", "tuic-1").First(&tuicInbound).Error; err != nil {
- t.Fatalf("read seeded row: %v", err)
- }
- relayPort := tuic.SOCKSPortForInbound(tuicInbound.Id)
- svc := &InboundService{}
- ctx, err := svc.loadPortConflictContext(database.GetDB(), nil)
- if err != nil {
- t.Fatalf("loadPortConflictContext: %v", err)
- }
- hit := svc.checkForwardedPortsConflict(ctx, fmt.Sprintf("%d", relayPort))
- if !strings.Contains(hit, "SOCKS5") {
- t.Fatalf("expected a collision naming the TUIC inbound's SOCKS5 relay port, got %q", hit)
- }
- }
- func TestCheckTuicSocksConflict_DisabledInboundRetainsReservation(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "tuic-disabled", "0.0.0.0", 8443, model.TUIC, ``, `{"clients":[{"uuid":"u","password":"p","email":"e"}]}`)
- var tuicIb model.Inbound
- if err := database.GetDB().Where("tag = ?", "tuic-disabled").First(&tuicIb).Error; err != nil {
- t.Fatalf("read seeded row: %v", err)
- }
- if err := database.GetDB().Model(&tuicIb).Update("enable", false).Error; err != nil {
- t.Fatalf("disable inbound: %v", err)
- }
- relayPort := tuic.SOCKSPortForInbound(tuicIb.Id)
- testIb := &model.Inbound{
- Tag: "conflict-test",
- Protocol: model.VLESS,
- Listen: "127.0.0.1",
- Port: relayPort,
- Enable: true,
- }
- conflict, err := checkTuicSocksConflict(database.GetDB(), testIb, 0, transportTCP)
- if err != nil {
- t.Fatalf("checkTuicSocksConflict: %v", err)
- }
- if conflict == nil {
- t.Fatal("expected conflict on disabled TUIC inbound's SOCKS port, got nil")
- }
- if conflict.InboundID != tuicIb.Id {
- t.Fatalf("expected conflict with inbound %d, got %d", tuicIb.Id, conflict.InboundID)
- }
- }
- func TestCheckTuicSocksRelayCollision(t *testing.T) {
- setupConflictDB(t)
- // Seed first TUIC inbound with ID 1
- ib1 := &model.Inbound{
- Id: 1,
- Tag: "tuic-1",
- Protocol: model.TUIC,
- Enable: true,
- Listen: "0.0.0.0",
- Port: 8443,
- Settings: `{"clients":[{"uuid":"a0000000-0000-0000-0000-000000000001","password":"p","email":"[email protected]"}]}`,
- }
- if err := database.GetDB().Create(ib1).Error; err != nil {
- t.Fatalf("seed ib1: %v", err)
- }
- // ID 1001 wraps to the same relay port (64001) as ID 1
- conflict, err := checkTuicSocksRelayCollision(database.GetDB(), 1001)
- if err != nil {
- t.Fatalf("checkTuicSocksRelayCollision: %v", err)
- }
- if conflict == nil {
- t.Fatal("expected collision between ID 1001 and ID 1, got nil")
- }
- if conflict.InboundID != 1 {
- t.Fatalf("expected collision with inbound 1, got %d", conflict.InboundID)
- }
- }
- func TestTuicSocksSelfConflict(t *testing.T) {
- ib := &model.Inbound{
- Protocol: model.TUIC,
- Listen: "127.0.0.1",
- Port: tuic.SOCKSPortForInbound(5),
- }
- errStr := tuicSocksSelfConflict(ib, 5)
- if errStr == "" {
- t.Fatal("expected self conflict error string, got empty")
- }
- if !strings.Contains(errStr, "own SOCKS5 relay port") {
- t.Fatalf("unexpected error string: %s", errStr)
- }
- // Different port should not conflict
- ib.Port = 9999
- if diff := tuicSocksSelfConflict(ib, 5); diff != "" {
- t.Fatalf("expected no conflict for different port, got %s", diff)
- }
- }
- func TestInboundTuicServerParsesLegacyFlatSettings(t *testing.T) {
- server := inboundTuicServer(string(model.TUIC), `{"certificate":"/cert.pem","private_key":"/secret-key.pem","congestion_control":" CuBiC ","udp_relay_mode":"quic","sni":"profile.example"}`)
- if server == nil {
- t.Fatal("expected legacy flat TUIC settings")
- }
- if server.CongestionControl != "cubic" || server.UDPRelayMode != "quic" || server.SNI != "profile.example" {
- t.Fatalf("legacy flat fields were not normalized: %+v", server)
- }
- if server.PrivateKey != "" {
- t.Fatal("client preview exposed the inbound private key")
- }
- }
- func TestNormalizeTuicSettingsCanonicalizesCongestionAndPacketLimit(t *testing.T) {
- ib := &model.Inbound{Protocol: model.TUIC, Settings: `{"congestion_control":"RENO","max_udp_relay_packet_size":65507,"server":{"congestion_control":" CuBiC ","max_udp_relay_packet_size":65500}}`}
- if err := normalizeTuicSettings(ib); err != nil {
- t.Fatalf("normalizeTuicSettings: %v", err)
- }
- var got struct {
- CongestionControl string `json:"congestion_control"`
- MaxPacketSize int `json:"max_udp_relay_packet_size"`
- Server struct {
- CongestionControl string `json:"congestion_control"`
- MaxPacketSize int `json:"max_udp_relay_packet_size"`
- } `json:"server"`
- }
- if err := json.Unmarshal([]byte(ib.Settings), &got); err != nil {
- t.Fatalf("unmarshal normalized settings: %v", err)
- }
- if got.CongestionControl != "new_reno" || got.Server.CongestionControl != "cubic" {
- t.Fatalf("congestion controllers were not canonicalized: %+v", got)
- }
- if got.MaxPacketSize != 65245 || got.Server.MaxPacketSize != 65245 {
- t.Fatalf("packet limits were not clamped: %+v", got)
- }
- ib.Settings = `{"server":{"congestion_control":"experimental"}}`
- if err := normalizeTuicSettings(ib); err == nil {
- t.Fatal("unsupported congestion controller was accepted")
- }
- }
|