relay_logging_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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. closeUniStream(t, auth)
  57. _, _ = authenticatedServerConnection(t, s, id)
  58. return s, c, id, password, token
  59. }
  60. func audit3LogsFor(marker string) string {
  61. var lines []string
  62. for _, line := range logger.GetLogs(10000, "DEBUG") {
  63. if strings.Contains(line, marker) {
  64. lines = append(lines, line)
  65. }
  66. }
  67. return strings.Join(lines, "\n")
  68. }
  69. func TestAudit3RealEventsRespectThresholdAndDoNotExposeSecrets(t *testing.T) {
  70. for _, level := range []string{"debug", "info", "warn", "error"} {
  71. t.Run(level, func(t *testing.T) {
  72. marker := fmt.Sprintf("audit3-logs-%s-%d", level, time.Now().UnixNano())
  73. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  74. defer cleanup()
  75. s, c, id, password, token := audit3LogsStart(t, level, marker, relayAddr)
  76. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  77. defer cancel()
  78. payload := "PAYLOAD-CANARY-" + marker
  79. u, err := c.OpenStreamSync(ctx)
  80. if err != nil {
  81. t.Fatal(err)
  82. }
  83. var frame bytes.Buffer
  84. frame.Write([]byte{5, 1})
  85. injectedDomain := "audit.invalid-FORGED-ENTRY-" + marker
  86. if err := WriteAddress(&frame, &Address{Type: AddrTypeDomain, Host: injectedDomain, Port: 443}); err != nil {
  87. t.Fatal(err)
  88. }
  89. frame.WriteString(payload)
  90. if _, err := u.Write(frame.Bytes()); err != nil {
  91. t.Fatal(err)
  92. }
  93. if err := u.Close(); err != nil {
  94. t.Fatal(err)
  95. }
  96. got := make([]byte, len(payload))
  97. if _, err := io.ReadFull(u, got); err != nil || string(got) != payload {
  98. t.Fatalf("TCP echo %q, %v", got, err)
  99. }
  100. if _, err := io.Copy(io.Discard, u); err != nil {
  101. t.Fatal(err)
  102. }
  103. u.CancelRead(0)
  104. var udp bytes.Buffer
  105. if err := WritePacket(&udp, 23456, 1, 1, 0, &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 53}, []byte(payload)); err != nil {
  106. t.Fatal(err)
  107. }
  108. if err := c.SendDatagram(udp.Bytes()); err != nil {
  109. t.Fatal(err)
  110. }
  111. if _, err := c.ReceiveDatagram(ctx); err != nil {
  112. t.Fatal(err)
  113. }
  114. dissociate, err := c.OpenUniStreamSync(ctx)
  115. if err != nil {
  116. t.Fatal(err)
  117. }
  118. if _, err := dissociate.Write([]byte{5, 3, 0x5b, 0xa0}); err != nil {
  119. t.Fatal(err)
  120. }
  121. _ = dissociate.Close()
  122. // The malformed frame includes traffic content as a canary; it must remain absent from logs.
  123. if err := c.SendDatagram(append([]byte{5, 2}, []byte(payload)...)); err != nil {
  124. t.Fatal(err)
  125. }
  126. bad, err := c.OpenStreamSync(ctx)
  127. if err != nil {
  128. t.Fatal(err)
  129. }
  130. if _, err := bad.Write([]byte{5, 1, 0xff}); err != nil {
  131. t.Fatal(err)
  132. }
  133. if _, err := io.Copy(io.Discard, bad); err != nil {
  134. t.Fatal(err)
  135. }
  136. _ = bad.Close()
  137. // Trigger a rejected Authenticate event using a changed token on the authenticated connection.
  138. badAuth, err := c.OpenUniStreamSync(ctx)
  139. if err != nil {
  140. t.Fatal(err)
  141. }
  142. wrongToken := bytes.Repeat([]byte{0x6d}, 32)
  143. badFrame := append([]byte{5, 0}, id[:]...)
  144. badFrame = append(badFrame, wrongToken...)
  145. if _, err := badAuth.Write(badFrame); err != nil {
  146. t.Fatal(err)
  147. }
  148. _ = badAuth.Close()
  149. select {
  150. case <-c.Context().Done():
  151. case <-ctx.Done():
  152. t.Fatal("bad auth did not close connection")
  153. }
  154. _ = s.packetConn.Close()
  155. deadline := time.Now().Add(2 * time.Second)
  156. for s.IsRunning() && time.Now().Before(deadline) {
  157. time.Sleep(time.Millisecond)
  158. }
  159. _ = s.Close()
  160. logs := audit3LogsFor(marker)
  161. for _, secret := range []string{password, id.String(), hex.EncodeToString(id[:]), hex.EncodeToString(token), hex.EncodeToString(wrongToken), payload, injectedDomain} {
  162. if strings.Contains(logs, secret) {
  163. t.Fatalf("logs expose canary %q", secret)
  164. }
  165. }
  166. wantInfo := level == "debug" || level == "info"
  167. wantWarn := level != "error"
  168. for _, event := range []string{"listener started", "client authenticated", "TCP relay started", "UDP association 23456 started", "listener stopped"} {
  169. if got := strings.Contains(logs, "): "+event); got != wantInfo {
  170. t.Errorf("event %q present=%t, want %t\n%s", event, got, wantInfo, logs)
  171. }
  172. }
  173. for _, event := range []string{"TCP relay failed", "client authentication rejected"} {
  174. if got := strings.Contains(logs, event); got != wantWarn {
  175. t.Errorf("event %q present=%t, want %t\n%s", event, got, wantWarn, logs)
  176. }
  177. }
  178. if got := strings.Contains(logs, "applied bbr congestion controller"); got != (level == "debug") {
  179. t.Errorf("debug controller event=%t", got)
  180. }
  181. if !strings.Contains(logs, "QUIC listener stopped accepting connections") {
  182. t.Errorf("actual listener error event missing at %s\n%s", level, logs)
  183. }
  184. t.Logf("actual logger events at %s: %d", level, strings.Count(logs, "tuic: inbound"))
  185. })
  186. }
  187. }
  188. func TestAudit3TCPFailuresMustNotFloodPanelLogs(t *testing.T) {
  189. marker := fmt.Sprintf("audit3-flood-%d", time.Now().UnixNano())
  190. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  191. defer cleanup()
  192. _, c, _, _, _ := audit3LogsStart(t, "warn", marker, relayAddr)
  193. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  194. defer cancel()
  195. for i := 0; i < 110; i++ {
  196. u, err := c.OpenStreamSync(ctx)
  197. if err != nil {
  198. t.Fatalf("CONNECT%d: %v", i, err)
  199. }
  200. if _, err := u.Write([]byte{5, 1, 0xff}); err != nil {
  201. t.Fatal(err)
  202. }
  203. if _, err := io.Copy(io.Discard, u); err != nil {
  204. t.Fatal(err)
  205. }
  206. _ = u.Close()
  207. }
  208. count := strings.Count(audit3LogsFor(marker), "TCP relay failed")
  209. if count != 1 {
  210. t.Fatalf("one authenticated QUIC connection emitted %d TCP failure warnings for 110 commands; expected a bounded warning category", count)
  211. }
  212. }
  213. func TestAudit3BiStreamCreditKeepsTCPEchoUsable(t *testing.T) {
  214. marker := fmt.Sprintf("audit3-credit-%d", time.Now().UnixNano())
  215. relayAddr, cleanup := startMockSocks5Server(t, "[email protected]", "audit3-socks-pass")
  216. defer cleanup()
  217. _, c, _, _, _ := audit3LogsStart(t, "error", marker, relayAddr)
  218. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  219. defer cancel()
  220. for i := 0; i < 110; i++ {
  221. openCtx, openCancel := context.WithTimeout(ctx, 700*time.Millisecond)
  222. u, err := c.OpenStreamSync(openCtx)
  223. openCancel()
  224. if err != nil {
  225. t.Fatalf("malformed stream%d open: %v", i, err)
  226. }
  227. if _, err := u.Write([]byte{5, 0xff}); err != nil {
  228. t.Fatal(err)
  229. }
  230. if _, err := io.Copy(io.Discard, u); err != nil {
  231. t.Fatal(err)
  232. }
  233. _ = u.Close()
  234. }
  235. u, err := c.OpenStreamSync(ctx)
  236. if err != nil {
  237. t.Fatal(err)
  238. }
  239. var frame bytes.Buffer
  240. frame.Write([]byte{5, 1})
  241. if err := WriteAddress(&frame, &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 443}); err != nil {
  242. t.Fatal(err)
  243. }
  244. frame.WriteString("after-credit-errors")
  245. if _, err := u.Write(frame.Bytes()); err != nil {
  246. t.Fatal(err)
  247. }
  248. _ = u.Close()
  249. result, err := io.ReadAll(u)
  250. if err != nil || string(result) != "after-credit-errors" {
  251. t.Fatalf("subsequent real TCP relay result=%q err=%v", result, err)
  252. }
  253. }