types_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package tuic
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. )
  8. func TestInstanceFromInbound(t *testing.T) {
  9. t.Run("valid settings", func(t *testing.T) {
  10. ib := &model.Inbound{
  11. Id: 10,
  12. Tag: "tuic-in-1",
  13. Port: 8443,
  14. Listen: "0.0.0.0",
  15. Protocol: model.TUIC,
  16. Settings: `{"certificate":"/etc/cert.pem","private_key":"/etc/key.pem","congestion_control":"bbr","alpn":["h3"],"clients":[{"uuid":"11111111-2222-3333-4444-555555555555","password":"pass1","email":"user1@test","enable":true}]}`,
  17. }
  18. inst, ok := InstanceFromInbound(ib)
  19. if !ok {
  20. t.Fatal("expected ok to be true")
  21. }
  22. if inst.Id != 10 || inst.Port != 8443 || inst.Tag != "tuic-in-1" {
  23. t.Fatalf("unexpected inst header fields: %+v", inst)
  24. }
  25. if inst.Certificate != "/etc/cert.pem" || inst.PrivateKey != "/etc/key.pem" {
  26. t.Fatalf("unexpected cert/key: %s / %s", inst.Certificate, inst.PrivateKey)
  27. }
  28. if len(inst.Clients) != 1 {
  29. t.Fatalf("expected 1 client, got %d", len(inst.Clients))
  30. }
  31. if inst.Clients[0].UUID != "11111111-2222-3333-4444-555555555555" || inst.Clients[0].Password != "pass1" {
  32. t.Fatalf("unexpected client: %+v", inst.Clients[0])
  33. }
  34. })
  35. t.Run("normalizes uuid to lowercase and trims space", func(t *testing.T) {
  36. ib := &model.Inbound{
  37. Id: 12,
  38. Protocol: model.TUIC,
  39. Settings: `{"clients":[{"uuid":" A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D ","password":"p"}]}`,
  40. }
  41. inst, ok := InstanceFromInbound(ib)
  42. if !ok || len(inst.Clients) != 1 {
  43. t.Fatal("expected ok and 1 client")
  44. }
  45. if inst.Clients[0].UUID != "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" {
  46. t.Fatalf("expected lowercase trimmed UUID, got %q", inst.Clients[0].UUID)
  47. }
  48. })
  49. t.Run("nil or wrong protocol", func(t *testing.T) {
  50. if _, ok := InstanceFromInbound(nil); ok {
  51. t.Fatal("expected false for nil")
  52. }
  53. if _, ok := InstanceFromInbound(&model.Inbound{Protocol: model.VLESS}); ok {
  54. t.Fatal("expected false for vless")
  55. }
  56. })
  57. t.Run("no enabled clients", func(t *testing.T) {
  58. ib := &model.Inbound{
  59. Id: 11,
  60. Protocol: model.TUIC,
  61. Settings: `{"clients":[{"uuid":"1111","password":"p","enable":false}]}`,
  62. }
  63. inst, ok := InstanceFromInbound(ib)
  64. if !ok {
  65. t.Fatal("expected ok for inbound")
  66. }
  67. if len(inst.Clients) != 0 {
  68. t.Fatalf("expected 0 enabled clients, got %d", len(inst.Clients))
  69. }
  70. })
  71. }
  72. func TestStructuralFingerprintDetectsCertificateRenewalAtSamePath(t *testing.T) {
  73. dir := t.TempDir()
  74. certPath := filepath.Join(dir, "fullchain.pem")
  75. keyPath := filepath.Join(dir, "privkey.pem")
  76. if err := os.WriteFile(certPath, []byte("certificate-v1"), 0o600); err != nil {
  77. t.Fatal(err)
  78. }
  79. if err := os.WriteFile(keyPath, []byte("key-v1"), 0o600); err != nil {
  80. t.Fatal(err)
  81. }
  82. inst := Instance{Listen: "0.0.0.0", Port: 443, Certificate: certPath, PrivateKey: keyPath}
  83. before := inst.StructuralFingerprint()
  84. if err := os.WriteFile(certPath, []byte("certificate-v2"), 0o600); err != nil {
  85. t.Fatal(err)
  86. }
  87. if before == inst.StructuralFingerprint() {
  88. t.Fatal("certificate renewal at the same path did not change the structural fingerprint")
  89. }
  90. }
  91. func TestFingerprints(t *testing.T) {
  92. inst1 := Instance{
  93. Id: 1,
  94. Port: 8443,
  95. Certificate: "/path/cert",
  96. PrivateKey: "/path/key",
  97. CongestionControl: "bbr",
  98. Clients: []TuicClientSettings{
  99. {UUID: "u1", Password: "p1", Email: "e1"},
  100. {UUID: "u2", Password: "p2", Email: "e2"},
  101. },
  102. }
  103. inst2 := Instance{
  104. Id: 1,
  105. Port: 8443,
  106. Certificate: "/path/cert",
  107. PrivateKey: "/path/key",
  108. CongestionControl: "bbr",
  109. Clients: []TuicClientSettings{
  110. {UUID: "u2", Password: "p2", Email: "e2"},
  111. {UUID: "u1", Password: "p1", Email: "e1"},
  112. },
  113. }
  114. if inst1.UsersFingerprint() != inst2.UsersFingerprint() {
  115. t.Fatalf("users fingerprint must be stable under reordering: %s vs %s", inst1.UsersFingerprint(), inst2.UsersFingerprint())
  116. }
  117. profileOnlyChange := inst1
  118. profileOnlyChange.CongestionControl = "new_reno"
  119. profileOnlyChange.UDPRelayMode = "quic"
  120. profileOnlyChange.SNI = "client-profile.example"
  121. if inst1.StructuralFingerprint() != profileOnlyChange.StructuralFingerprint() {
  122. t.Fatal("client-profile defaults must not restart the native TUIC listener")
  123. }
  124. packetLimitChange := inst1
  125. packetLimitChange.MaxUdpRelayPacketSize = 4096
  126. if inst1.StructuralFingerprint() == packetLimitChange.StructuralFingerprint() {
  127. t.Fatal("changing the UDP packet limit must update the native TUIC listener")
  128. }
  129. }
  130. func TestBindTo(t *testing.T) {
  131. tests := []struct {
  132. listen string
  133. want string
  134. }{
  135. {"", ":8443"},
  136. {"127.0.0.1", "127.0.0.1:8443"},
  137. {"::", "[::]:8443"},
  138. {"2001:db8::1", "[2001:db8::1]:8443"},
  139. }
  140. for _, tc := range tests {
  141. t.Run(tc.listen, func(t *testing.T) {
  142. got := Instance{Listen: tc.listen, Port: 8443}.BindTo()
  143. if got != tc.want {
  144. t.Fatalf("BindTo(%q) = %q, want %q", tc.listen, got, tc.want)
  145. }
  146. })
  147. }
  148. }