relay_logging_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package tuic
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. "encoding/hex"
  7. "fmt"
  8. "io"
  9. "net"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/google/uuid"
  14. clientquic "github.com/quic-go/quic-go"
  15. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  16. )
  17. func audit3LogsStart(t *testing.T, level, marker, relayAddr string) (*Server, *clientquic.Conn, uuid.UUID, string, []byte) {
  18. t.Helper()
  19. cert, key := generateTestCert(t)
  20. id := uuid.New()
  21. password := "PASSWORD-CANARY-" + marker
  22. s, err := NewServer(Instance{
  23. Id: 192301, Tag: marker, Listen: "127.0.0.1", Port: 0,
  24. Certificate: string(cert), PrivateKey: string(key), ALPN: []string{"h3"},
  25. AuthenticationTimeout: 2, MaxIdleTime: 30, LogLevel: level,
  26. Clients: []TuicClientSettings{{UUID: id.String(), Password: password, Email: "[email protected]"}},
  27. }, &SocksRelay{Addr: relayAddr, Password: "audit3-socks-pass"})
  28. if err != nil {
  29. t.Fatal(err)
  30. }
  31. if err := s.Start(); err != nil {
  32. t.Fatal(err)
  33. }
  34. t.Cleanup(func() { _ = s.Close() })
  35. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  36. defer cancel()
  37. c, err := clientquic.DialAddr(ctx, s.packetConn.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{"h3"}}, &clientquic.Config{EnableDatagrams: true})
  38. if err != nil {
  39. t.Fatal(err)
  40. }
  41. t.Cleanup(func() { _ = c.CloseWithError(0, "audit3 finished") })
  42. tlsState := c.ConnectionState().TLS
  43. token, err := tlsState.ExportKeyingMaterial(string(id[:]), []byte(password), 32)
  44. if err != nil {
  45. t.Fatal(err)
  46. }
  47. auth, err := c.OpenUniStreamSync(ctx)
  48. if err != nil {
  49. t.Fatal(err)
  50. }
  51. frame := append([]byte{5, 0}, id[:]...)
  52. frame = append(frame, token...)
  53. if _, err := auth.Write(frame); err != nil {
  54. t.Fatal(err)
  55. }
  56. if err := auth.Close(); err != nil {
  57. t.Fatal(err)
  58. }
  59. _, _ = authenticatedServerConnection(t, s, id)
  60. return s, c, id, password, token
  61. }
  62. func audit3LogsFor(marker string) string {
  63. var lines []string
  64. for _, line := range logger.GetLogs(10000, "DEBUG") {
  65. if strings.Contains(line, marker) {
  66. lines = append(lines, line)
  67. }
  68. }
  69. return strings.Join(lines, "\n")
  70. }
  71. func TestAudit3RealEventsRespectThresholdAndDoNotExposeSecrets(t *testing.T) {
  72. for _, level := range []string{"debug", "info", "warn", "error"} {
  73. t.Run(level, func(t *testing.T) {
  74. marker := fmt.Sprintf("audit3-logs-%s-%d", level, time.Now().UnixNano())
  75. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  76. defer cleanup()
  77. s, c, id, password, token := audit3LogsStart(t, level, marker, relayAddr)
  78. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  79. defer cancel()
  80. payload := "PAYLOAD-CANARY-" + marker
  81. u, err := c.OpenStreamSync(ctx)
  82. if err != nil {
  83. t.Fatal(err)
  84. }
  85. var frame bytes.Buffer
  86. frame.Write([]byte{5, 1})
  87. injectedDomain := "audit.invalid-FORGED-ENTRY-" + marker
  88. if err := WriteAddress(&frame, &Address{Type: AddrTypeDomain, Host: injectedDomain, Port: 443}); err != nil {
  89. t.Fatal(err)
  90. }
  91. frame.WriteString(payload)
  92. if _, err := u.Write(frame.Bytes()); err != nil {
  93. t.Fatal(err)
  94. }
  95. if err := u.Close(); err != nil {
  96. t.Fatal(err)
  97. }
  98. got := make([]byte, len(payload))
  99. if _, err := io.ReadFull(u, got); err != nil || string(got) != payload {
  100. t.Fatalf("TCP echo %q, %v", got, err)
  101. }
  102. if _, err := io.Copy(io.Discard, u); err != nil {
  103. t.Fatal(err)
  104. }
  105. u.CancelRead(0)
  106. var udp bytes.Buffer
  107. if err := WritePacket(&udp, 23456, 1, 1, 0, &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 53}, []byte(payload)); err != nil {
  108. t.Fatal(err)
  109. }
  110. if err := c.SendDatagram(udp.Bytes()); err != nil {
  111. t.Fatal(err)
  112. }
  113. if _, err := c.ReceiveDatagram(ctx); err != nil {
  114. t.Fatal(err)
  115. }
  116. dissociate, err := c.OpenUniStreamSync(ctx)
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. if _, err := dissociate.Write([]byte{5, 3, 0x5b, 0xa0}); err != nil {
  121. t.Fatal(err)
  122. }
  123. _ = dissociate.Close()
  124. // The malformed frame includes traffic content as a canary; it must remain absent from logs.
  125. if err := c.SendDatagram(append([]byte{5, 2}, []byte(payload)...)); err != nil {
  126. t.Fatal(err)
  127. }
  128. bad, err := c.OpenStreamSync(ctx)
  129. if err != nil {
  130. t.Fatal(err)
  131. }
  132. if _, err := bad.Write([]byte{5, 1, 0xff}); err != nil {
  133. t.Fatal(err)
  134. }
  135. if _, err := io.Copy(io.Discard, bad); err != nil {
  136. t.Fatal(err)
  137. }
  138. _ = bad.Close()
  139. // Trigger a rejected Authenticate event using a changed token on the authenticated connection.
  140. badAuth, err := c.OpenUniStreamSync(ctx)
  141. if err != nil {
  142. t.Fatal(err)
  143. }
  144. wrongToken := bytes.Repeat([]byte{0x6d}, 32)
  145. badFrame := append([]byte{5, 0}, id[:]...)
  146. badFrame = append(badFrame, wrongToken...)
  147. if _, err := badAuth.Write(badFrame); err != nil {
  148. t.Fatal(err)
  149. }
  150. _ = badAuth.Close()
  151. select {
  152. case <-c.Context().Done():
  153. case <-ctx.Done():
  154. t.Fatal("bad auth did not close connection")
  155. }
  156. _ = s.packetConn.Close()
  157. deadline := time.Now().Add(2 * time.Second)
  158. for s.IsRunning() && time.Now().Before(deadline) {
  159. time.Sleep(time.Millisecond)
  160. }
  161. _ = s.Close()
  162. logs := audit3LogsFor(marker)
  163. for _, secret := range []string{password, id.String(), hex.EncodeToString(id[:]), hex.EncodeToString(token), hex.EncodeToString(wrongToken), payload, injectedDomain} {
  164. if strings.Contains(logs, secret) {
  165. t.Fatalf("logs expose canary %q", secret)
  166. }
  167. }
  168. wantInfo := level == "debug" || level == "info"
  169. wantWarn := level != "error"
  170. for _, event := range []string{"listener started", "client authenticated", "TCP relay started", "UDP association 23456 started", "listener stopped"} {
  171. if got := strings.Contains(logs, "): "+event); got != wantInfo {
  172. t.Errorf("event %q present=%t, want %t\n%s", event, got, wantInfo, logs)
  173. }
  174. }
  175. for _, event := range []string{"TCP relay failed", "client authentication rejected"} {
  176. if got := strings.Contains(logs, event); got != wantWarn {
  177. t.Errorf("event %q present=%t, want %t\n%s", event, got, wantWarn, logs)
  178. }
  179. }
  180. if got := strings.Contains(logs, "applied bbr congestion controller"); got != (level == "debug") {
  181. t.Errorf("debug controller event=%t", got)
  182. }
  183. if !strings.Contains(logs, "QUIC listener stopped accepting connections") {
  184. t.Errorf("actual listener error event missing at %s\n%s", level, logs)
  185. }
  186. t.Logf("actual logger events at %s: %d", level, strings.Count(logs, "tuic: inbound"))
  187. })
  188. }
  189. }
  190. func TestAudit3TCPFailuresMustNotFloodPanelLogs(t *testing.T) {
  191. marker := fmt.Sprintf("audit3-flood-%d", time.Now().UnixNano())
  192. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  193. defer cleanup()
  194. _, c, _, _, _ := audit3LogsStart(t, "warn", marker, relayAddr)
  195. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  196. defer cancel()
  197. for i := 0; i < 110; i++ {
  198. u, err := c.OpenStreamSync(ctx)
  199. if err != nil {
  200. t.Fatalf("CONNECT%d: %v", i, err)
  201. }
  202. if _, err := u.Write([]byte{5, 1, 0xff}); err != nil {
  203. t.Fatal(err)
  204. }
  205. if _, err := io.Copy(io.Discard, u); err != nil {
  206. t.Fatal(err)
  207. }
  208. _ = u.Close()
  209. }
  210. count := strings.Count(audit3LogsFor(marker), "TCP relay failed")
  211. if count != 1 {
  212. t.Fatalf("one authenticated QUIC connection emitted %d TCP failure warnings for 110 commands; expected a bounded warning category", count)
  213. }
  214. }
  215. func TestAudit3BiStreamCreditKeepsTCPEchoUsable(t *testing.T) {
  216. marker := fmt.Sprintf("audit3-credit-%d", time.Now().UnixNano())
  217. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  218. defer cleanup()
  219. _, c, _, _, _ := audit3LogsStart(t, "error", marker, relayAddr)
  220. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  221. defer cancel()
  222. for i := 0; i < 110; i++ {
  223. openCtx, openCancel := context.WithTimeout(ctx, 700*time.Millisecond)
  224. u, err := c.OpenStreamSync(openCtx)
  225. openCancel()
  226. if err != nil {
  227. t.Fatalf("malformed stream%d open: %v", i, err)
  228. }
  229. if _, err := u.Write([]byte{5, 0xff}); err != nil {
  230. t.Fatal(err)
  231. }
  232. if _, err := io.Copy(io.Discard, u); err != nil {
  233. t.Fatal(err)
  234. }
  235. _ = u.Close()
  236. }
  237. u, err := c.OpenStreamSync(ctx)
  238. if err != nil {
  239. t.Fatal(err)
  240. }
  241. var frame bytes.Buffer
  242. frame.Write([]byte{5, 1})
  243. if err := WriteAddress(&frame, &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 443}); err != nil {
  244. t.Fatal(err)
  245. }
  246. frame.WriteString("after-credit-errors")
  247. if _, err := u.Write(frame.Bytes()); err != nil {
  248. t.Fatal(err)
  249. }
  250. _ = u.Close()
  251. result, err := io.ReadAll(u)
  252. if err != nil || string(result) != "after-credit-errors" {
  253. t.Fatalf("subsequent real TCP relay result=%q err=%v", result, err)
  254. }
  255. }