relay_recovery_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package tuic
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/binary"
  6. "io"
  7. "net"
  8. "runtime"
  9. "strings"
  10. "testing"
  11. "time"
  12. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  13. clientquic "github.com/quic-go/quic-go"
  14. )
  15. func audit3RestartableSOCKS(t *testing.T) (string, *net.UDPConn, *net.UDPAddr) {
  16. t.Helper()
  17. u, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  18. if err != nil {
  19. t.Fatal(err)
  20. }
  21. udpAddr := u.LocalAddr().(*net.UDPAddr)
  22. ln, err := net.Listen("tcp4", "127.0.0.1:0")
  23. if err != nil {
  24. t.Fatal(err)
  25. }
  26. t.Cleanup(func() { _ = ln.Close(); _ = u.Close() })
  27. go func() {
  28. for {
  29. c, err := ln.Accept()
  30. if err != nil {
  31. return
  32. }
  33. go func(c net.Conn) {
  34. defer c.Close()
  35. var greet [4]byte
  36. if _, err := io.ReadFull(c, greet[:]); err != nil {
  37. return
  38. }
  39. if _, err := c.Write([]byte{5, 0}); err != nil {
  40. return
  41. }
  42. var req [10]byte
  43. if _, err := io.ReadFull(c, req[:]); err != nil {
  44. return
  45. }
  46. reply := []byte{5, 0, 0, 1, 127, 0, 0, 1, 0, 0}
  47. binary.BigEndian.PutUint16(reply[8:], uint16(udpAddr.Port))
  48. if _, err := c.Write(reply); err != nil {
  49. return
  50. }
  51. _, _ = io.Copy(io.Discard, c)
  52. }(c)
  53. }
  54. }()
  55. return ln.Addr().String(), u, udpAddr
  56. }
  57. func audit3StartUDPEcho(u *net.UDPConn, arrived chan<- struct{}) {
  58. go func() {
  59. buf := make([]byte, 2048)
  60. for {
  61. n, src, err := u.ReadFromUDP(buf)
  62. if err != nil {
  63. return
  64. }
  65. _, _ = u.WriteToUDP(buf[:n], src)
  66. if arrived != nil {
  67. select {
  68. case arrived <- struct{}{}:
  69. default:
  70. }
  71. }
  72. }
  73. }()
  74. }
  75. func audit3SendPacket(t *testing.T, c *clientquic.Conn, mode uint8, assoc, pkt uint16, payload string) {
  76. t.Helper()
  77. var b bytes.Buffer
  78. if err := WritePacket(&b, assoc, pkt, 1, 0, &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 53}, []byte(payload)); err != nil {
  79. t.Fatal(err)
  80. }
  81. if mode == packetTransportDatagram {
  82. if err := c.SendDatagram(b.Bytes()); err != nil {
  83. t.Fatal(err)
  84. }
  85. return
  86. }
  87. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  88. defer cancel()
  89. s, err := c.OpenUniStreamSync(ctx)
  90. if err != nil {
  91. t.Fatal(err)
  92. }
  93. if _, err := s.Write(b.Bytes()); err != nil {
  94. t.Fatal(err)
  95. }
  96. _ = s.Close()
  97. }
  98. func audit3ReceivePacket(c *clientquic.Conn, mode uint8, duration time.Duration) (*PacketHeader, []byte, error) {
  99. ctx, cancel := context.WithTimeout(context.Background(), duration)
  100. defer cancel()
  101. var r io.Reader
  102. if mode == packetTransportDatagram {
  103. b, err := c.ReceiveDatagram(ctx)
  104. if err != nil {
  105. return nil, nil, err
  106. }
  107. r = bytes.NewReader(b)
  108. } else {
  109. s, err := c.AcceptUniStream(ctx)
  110. if err != nil {
  111. return nil, nil, err
  112. }
  113. defer s.CancelRead(0)
  114. _ = s.SetReadDeadline(time.Now().Add(duration))
  115. r = s
  116. }
  117. if _, _, err := ReadCommand(r); err != nil {
  118. return nil, nil, err
  119. }
  120. h, err := ReadPacketHeader(r)
  121. if err != nil {
  122. return nil, nil, err
  123. }
  124. p, err := readPacketPayload(r, h)
  125. return h, p, err
  126. }
  127. func TestAudit3UDPAssociationMustRecoverAfterBridgeReadFailure(t *testing.T) {
  128. if runtime.GOOS == "windows" {
  129. t.Skip("Go disables SIO_UDP_CONNRESET on Windows, so a dead UDP bridge never fails a read there")
  130. }
  131. for _, mode := range []uint8{packetTransportDatagram, packetTransportStream} {
  132. name := "datagram"
  133. if mode == packetTransportStream {
  134. name = "stream"
  135. }
  136. t.Run(name, func(t *testing.T) {
  137. relayAddr, u, udpAddr := audit3RestartableSOCKS(t)
  138. audit3StartUDPEcho(u, nil)
  139. s, c, id, _ := startLifecycleTestServer(t, relayAddr, "audit3-recovery@x")
  140. _, user := authenticatedServerConnection(t, s, id)
  141. audit3SendPacket(t, c, mode, 42131, 1, "before")
  142. if _, p, err := audit3ReceivePacket(c, mode, time.Second); err != nil || string(p) != "before" {
  143. t.Fatalf("initial echo %q %v", p, err)
  144. }
  145. s.UpdateRuntimeSettings("recovery-"+name, "bbr", "warn")
  146. _ = u.Close()
  147. audit3SendPacket(t, c, mode, 42131, 2, "while-down")
  148. deadline := time.Now().Add(2 * time.Second)
  149. for {
  150. found := false
  151. for _, line := range logger.GetLogs(10000, "DEBUG") {
  152. if strings.Contains(line, "recovery-"+name) && strings.Contains(line, "UDP relay receive failed") {
  153. found = true
  154. break
  155. }
  156. }
  157. if found {
  158. break
  159. }
  160. if time.Now().After(deadline) {
  161. t.Fatal("closed UDP bridge did not terminate the response reader")
  162. }
  163. time.Sleep(time.Millisecond)
  164. }
  165. u2, err := net.ListenUDP("udp4", udpAddr)
  166. if err != nil {
  167. t.Fatal(err)
  168. }
  169. t.Cleanup(func() { _ = u2.Close() })
  170. arrived := make(chan struct{}, 4)
  171. audit3StartUDPEcho(u2, arrived)
  172. audit3SendPacket(t, c, mode, 42131, 3, "after")
  173. select {
  174. case <-arrived:
  175. case <-time.After(time.Second):
  176. t.Fatal("restarted bridge did not receive the retained association request")
  177. }
  178. _, payload, oldErr := audit3ReceivePacket(c, mode, 300*time.Millisecond)
  179. // A different association proves QUIC, the restarted SOCKS bridge, and both transport modes remain functional.
  180. audit3SendPacket(t, c, mode, 42132, 4, "fresh")
  181. h, p, newErr := audit3ReceivePacket(c, mode, time.Second)
  182. if newErr != nil || h.AssocID != 42132 || string(p) != "fresh" {
  183. t.Fatalf("fresh association probe %v %q %v", h, p, newErr)
  184. }
  185. if oldErr != nil || string(payload) != "after" {
  186. t.Fatalf("retained association became receive blackhole after read failure: response %q err=%v; fresh association works; user up=%d down=%d", payload, oldErr, user.Traffic.BytesUp.Load(), user.Traffic.BytesDown.Load())
  187. }
  188. })
  189. }
  190. }
  191. func TestAudit3FirstTransportReturnsPositiveEchoAfterOppositeModePacket(t *testing.T) {
  192. for _, first := range []uint8{packetTransportDatagram, packetTransportStream} {
  193. name := "datagram-first"
  194. if first == packetTransportStream {
  195. name = "stream-first"
  196. }
  197. t.Run(name, func(t *testing.T) {
  198. relayAddr, u, _ := audit3RestartableSOCKS(t)
  199. audit3StartUDPEcho(u, nil)
  200. _, c, _, _ := startLifecycleTestServer(t, relayAddr, "audit3-first-mode@x")
  201. audit3SendPacket(t, c, first, 43221, 1, "first")
  202. if _, p, err := audit3ReceivePacket(c, first, time.Second); err != nil || string(p) != "first" {
  203. t.Fatalf("initial mode echo %q %v", p, err)
  204. }
  205. audit3SendPacket(t, c, 1-first, 43221, 2, "opposite")
  206. if _, p, err := audit3ReceivePacket(c, first, time.Second); err != nil || string(p) != "opposite" {
  207. t.Fatalf("opposite request response did not keep first transport: %q %v", p, err)
  208. }
  209. })
  210. }
  211. }
  212. func TestUDPReaderCleanupCannotDeleteReplacementAssociation(t *testing.T) {
  213. registry := newUdpAssociationRegistry(1500)
  214. header := &PacketHeader{AssocID: 7, FragTotal: 1, Size: 1, Addr: &Address{Type: AddrTypeIPv4, IP: net.IPv4(1, 1, 1, 1), Port: 53}}
  215. old, _, _, complete := registry.feed(packetTransportDatagram, header, []byte("a"))
  216. if !complete {
  217. t.Fatal("first packet incomplete")
  218. }
  219. registry.dissociate(7)
  220. replacement, _, _, complete := registry.feed(packetTransportStream, header, []byte("b"))
  221. if !complete || replacement == old {
  222. t.Fatal("association generation was reused")
  223. }
  224. registry.release(7, old)
  225. if !registry.touch(7, replacement, time.Now()) {
  226. t.Fatal("late reader cleanup deleted the new association")
  227. }
  228. registry.release(7, replacement)
  229. if registry.touch(7, replacement, time.Now()) {
  230. t.Fatal("current reader cleanup retained dead association")
  231. }
  232. }