service_tuic_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package sub
  2. import (
  3. "net/url"
  4. "reflect"
  5. "strings"
  6. "testing"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. )
  9. func TestGenTuicLinkBasic(t *testing.T) {
  10. inbound := &model.Inbound{
  11. Listen: "198.51.100.1",
  12. Port: 8443,
  13. Protocol: model.TUIC,
  14. Remark: "tuic-test",
  15. Settings: `{"server":{"certificate":"/path/cert","private_key":"/path/key"},"clients":[{"uuid":"11111111-1111-1111-1111-111111111111","password":"testpassword","email":"user@test"}]}`,
  16. }
  17. s := &SubService{}
  18. link := s.genTuicLink(inbound, "user@test")
  19. u, err := url.Parse(link)
  20. if err != nil {
  21. t.Fatalf("link does not parse: %v\ngot: %s", err, link)
  22. }
  23. if u.Scheme != "tuic" {
  24. t.Fatalf("scheme = %q, want tuic", u.Scheme)
  25. }
  26. if u.Host != "198.51.100.1:8443" {
  27. t.Fatalf("host = %q, want 198.51.100.1:8443", u.Host)
  28. }
  29. if u.User.Username() != "11111111-1111-1111-1111-111111111111" {
  30. t.Fatalf("username = %q, want uuid", u.User.Username())
  31. }
  32. pass, ok := u.User.Password()
  33. if !ok || pass != "testpassword" {
  34. t.Fatalf("password = %q, want testpassword", pass)
  35. }
  36. q := u.Query()
  37. if q.Get("congestion_control") != "bbr" {
  38. t.Fatalf("congestion_control = %q, want bbr", q.Get("congestion_control"))
  39. }
  40. if q.Get("allow_insecure") != "0" {
  41. t.Fatalf("allow_insecure = %q, want 0", q.Get("allow_insecure"))
  42. }
  43. if q.Get("alpn") != "h3,spdy/3.1" {
  44. t.Fatalf("alpn = %q, want h3,spdy/3.1", q.Get("alpn"))
  45. }
  46. if q.Get("udp_relay_mode") != "native" {
  47. t.Fatalf("udp_relay_mode = %q, want native", q.Get("udp_relay_mode"))
  48. }
  49. if u.Fragment != "tuic-test-user@test" {
  50. t.Fatalf("fragment = %q, want tuic-test-user@test", u.Fragment)
  51. }
  52. }
  53. func TestGenTuicLinkExplicitParams(t *testing.T) {
  54. inbound := &model.Inbound{
  55. Listen: "198.51.100.1",
  56. Port: 8443,
  57. Protocol: model.TUIC,
  58. Remark: "tuic-test",
  59. Settings: `{"server":{"certificate":"/path/cert","private_key":"/path/key","congestion_control":"cubic","alpn":["h3"],"sni":"tuic.example.com","udp_relay_mode":"quic"},"clients":[{"uuid":"11111111-1111-1111-1111-111111111111","password":"testpassword","email":"user@test"}]}`,
  60. }
  61. s := &SubService{}
  62. link := s.genTuicLink(inbound, "user@test")
  63. u, err := url.Parse(link)
  64. if err != nil {
  65. t.Fatalf("link does not parse: %v\ngot: %s", err, link)
  66. }
  67. q := u.Query()
  68. if q.Get("congestion_control") != "cubic" {
  69. t.Fatalf("congestion_control = %q, want cubic", q.Get("congestion_control"))
  70. }
  71. if q.Get("alpn") != "h3" {
  72. t.Fatalf("alpn = %q, want h3", q.Get("alpn"))
  73. }
  74. if q.Get("sni") != "tuic.example.com" {
  75. t.Fatalf("sni = %q, want tuic.example.com", q.Get("sni"))
  76. }
  77. if q.Get("udp_relay_mode") != "quic" {
  78. t.Fatalf("udp_relay_mode = %q, want quic", q.Get("udp_relay_mode"))
  79. }
  80. }
  81. func TestGenTuicLinkExternalProxyFanOut(t *testing.T) {
  82. inbound := &model.Inbound{
  83. Listen: "0.0.0.0",
  84. Port: 8443,
  85. Protocol: model.TUIC,
  86. Remark: "tuic-base",
  87. Settings: `{"server":{"certificate":"/path/cert","private_key":"/path/key"},"clients":[{"uuid":"11111111-1111-1111-1111-111111111111","password":"testpassword","email":"user@test"}]}`,
  88. StreamSettings: `{"externalProxy":[{"dest":"host1.example.com","port":9443,"remark":"US"},{"dest":"host2.example.com","port":10443,"remark":"EU","allowInsecure":true}]}`,
  89. }
  90. s := &SubService{}
  91. links := s.genTuicLink(inbound, "user@test")
  92. lines := strings.Split(strings.TrimSpace(links), "\n")
  93. if len(lines) != 2 {
  94. t.Fatalf("expected 2 links for externalProxy fan-out, got %d:\n%s", len(lines), links)
  95. }
  96. u1, err := url.Parse(lines[0])
  97. if err != nil {
  98. t.Fatalf("first link does not parse: %v", err)
  99. }
  100. if u1.Host != "host1.example.com:9443" {
  101. t.Fatalf("first host = %q, want host1.example.com:9443", u1.Host)
  102. }
  103. if !strings.Contains(u1.Fragment, "US") {
  104. t.Fatalf("first fragment = %q, want to contain US", u1.Fragment)
  105. }
  106. if u1.Query().Get("allow_insecure") != "0" {
  107. t.Fatalf("first allow_insecure = %q, want 0", u1.Query().Get("allow_insecure"))
  108. }
  109. if u1.Query().Has("allowInsecure") {
  110. t.Fatalf("first link should not have allowInsecure")
  111. }
  112. u2, err := url.Parse(lines[1])
  113. if err != nil {
  114. t.Fatalf("second link does not parse: %v", err)
  115. }
  116. if u2.Host != "host2.example.com:10443" {
  117. t.Fatalf("second host = %q, want host2.example.com:10443", u2.Host)
  118. }
  119. if !strings.Contains(u2.Fragment, "EU") {
  120. t.Fatalf("second fragment = %q, want to contain EU", u2.Fragment)
  121. }
  122. if u2.Query().Get("allow_insecure") != "1" {
  123. t.Fatalf("second allow_insecure = %q, want 1", u2.Query().Get("allow_insecure"))
  124. }
  125. if u2.Query().Has("allowInsecure") {
  126. t.Fatalf("second link should not have allowInsecure")
  127. }
  128. }
  129. func TestBuildTuicProxy_ExternalProxyOverrides(t *testing.T) {
  130. svc := &SubClashService{SubService: &SubService{address: "sub.example.com"}}
  131. inbound := &model.Inbound{
  132. Listen: "198.51.100.1",
  133. Port: 8443,
  134. Protocol: model.TUIC,
  135. Remark: "tuic-base",
  136. Settings: `{"server":{"certificate":"/path/cert","private_key":"/path/key","sni":"base.example.com","alpn":["h3"]},"clients":[{"uuid":"11111111-1111-1111-1111-111111111111","password":"testpassword","email":"user@test"}]}`,
  137. }
  138. client := model.Client{Email: "user@test"}
  139. // 1. Without overrides from ep
  140. baseProxy := svc.buildTuicProxy(svc.SubService, inbound, client, nil)
  141. if baseProxy == nil {
  142. t.Fatal("baseProxy is nil")
  143. }
  144. if baseProxy["sni"] != "base.example.com" {
  145. t.Fatalf("base sni = %v, want base.example.com", baseProxy["sni"])
  146. }
  147. if !reflect.DeepEqual(baseProxy["alpn"], []string{"h3"}) {
  148. t.Fatalf("base alpn = %v, want [h3]", baseProxy["alpn"])
  149. }
  150. if _, ok := baseProxy["skip-cert-verify"]; ok {
  151. t.Fatalf("base skip-cert-verify should not be set")
  152. }
  153. // 2. With overrides from ep
  154. ep := map[string]any{
  155. "dest": "custom.example.com",
  156. "port": float64(9443),
  157. "remark": "custom-node",
  158. "sni": "custom.sni.com",
  159. "alpn": "h3,spdy/3.1",
  160. "allowInsecure": true,
  161. }
  162. proxy := svc.buildTuicProxy(svc.SubService, inbound, client, ep)
  163. if proxy == nil {
  164. t.Fatal("proxy is nil")
  165. }
  166. if proxy["sni"] != "custom.sni.com" {
  167. t.Fatalf("sni = %v, want custom.sni.com", proxy["sni"])
  168. }
  169. if !reflect.DeepEqual(proxy["alpn"], []string{"h3", "spdy/3.1"}) {
  170. t.Fatalf("alpn = %v, want [h3 spdy/3.1]", proxy["alpn"])
  171. }
  172. if proxy["skip-cert-verify"] != true {
  173. t.Fatalf("skip-cert-verify = %v, want true", proxy["skip-cert-verify"])
  174. }
  175. }