relay_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. package amneziawgnet
  2. import (
  3. "errors"
  4. "io"
  5. "net"
  6. "net/netip"
  7. "os"
  8. "testing"
  9. "time"
  10. )
  11. // newDeadUDPSession builds a socks5UDPSession over real but already-closed
  12. // sockets, so pump's receive fails immediately and its teardown runs at once.
  13. func newDeadUDPSession(t *testing.T) *socks5UDPSession {
  14. t.Helper()
  15. peer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  16. if err != nil {
  17. t.Fatalf("listen udp: %v", err)
  18. }
  19. t.Cleanup(func() { _ = peer.Close() })
  20. udpConn, err := net.DialUDP("udp", nil, peer.LocalAddr().(*net.UDPAddr))
  21. if err != nil {
  22. t.Fatalf("dial udp: %v", err)
  23. }
  24. ctrlLn, err := net.Listen("tcp", "127.0.0.1:0")
  25. if err != nil {
  26. t.Fatalf("listen tcp: %v", err)
  27. }
  28. t.Cleanup(func() { _ = ctrlLn.Close() })
  29. ctrl, err := net.Dial("tcp", ctrlLn.Addr().String())
  30. if err != nil {
  31. t.Fatalf("dial tcp: %v", err)
  32. }
  33. _ = udpConn.Close()
  34. return &socks5UDPSession{ctrl: ctrl, udpConn: udpConn}
  35. }
  36. // TestUDPRelayPumpOnlyRetiresItsOwnSession pins the flow that survives a
  37. // duplicate associate: a losing pump must not evict the published session.
  38. func TestUDPRelayPumpOnlyRetiresItsOwnSession(t *testing.T) {
  39. relay := NewUDPRelay(SocksRelay{Addr: "127.0.0.1:1", Password: "x"}, nil)
  40. src := netip.MustParseAddrPort("10.8.1.5:51820")
  41. live := newDeadUDPSession(t)
  42. superseded := newDeadUDPSession(t)
  43. relay.sessions[src] = live
  44. // Returns as soon as receive fails on the closed socket, so no wait is needed.
  45. relay.pump(src, superseded)
  46. got, ok := relay.sessions[src]
  47. if !ok {
  48. t.Fatal("live session was evicted: a retiring pump deleted src's entry regardless of which session held it")
  49. }
  50. if got != live {
  51. t.Fatalf("sessions[%v] = %p, want the live session %p", src, got, live)
  52. }
  53. }
  54. // TestUDPRelayCloseDropsEverySession keeps Close's contract explicit now that
  55. // pump's teardown is conditional on still owning the key.
  56. func TestUDPRelayCloseDropsEverySession(t *testing.T) {
  57. relay := NewUDPRelay(SocksRelay{Addr: "127.0.0.1:1", Password: "x"}, nil)
  58. for _, s := range []string{"10.8.1.5:51820", "10.8.1.6:2000"} {
  59. relay.sessions[netip.MustParseAddrPort(s)] = newDeadUDPSession(t)
  60. }
  61. relay.Close()
  62. if n := len(relay.sessions); n != 0 {
  63. t.Fatalf("Close left %d sessions behind, want 0", n)
  64. }
  65. }
  66. // tcpPair returns a connected pair of real loopback TCP conns; net.Pipe would
  67. // not do, since these tests turn on CloseWrite, which it does not implement.
  68. func tcpPair(t *testing.T) (client, server net.Conn) {
  69. t.Helper()
  70. ln, err := net.Listen("tcp", "127.0.0.1:0")
  71. if err != nil {
  72. t.Fatalf("listen: %v", err)
  73. }
  74. defer ln.Close()
  75. type accepted struct {
  76. conn net.Conn
  77. err error
  78. }
  79. ch := make(chan accepted, 1)
  80. go func() {
  81. c, err := ln.Accept()
  82. ch <- accepted{c, err}
  83. }()
  84. client, err = net.Dial("tcp", ln.Addr().String())
  85. if err != nil {
  86. t.Fatalf("dial: %v", err)
  87. }
  88. got := <-ch
  89. if got.err != nil {
  90. t.Fatalf("accept: %v", got.err)
  91. }
  92. t.Cleanup(func() { _ = client.Close(); _ = got.conn.Close() })
  93. return client, got.conn
  94. }
  95. // TestPipeBothWaysDeliversReplyAfterHalfClose is the half-close regression: a
  96. // client that shuts down its write side must still receive the full response.
  97. func TestPipeBothWaysDeliversReplyAfterHalfClose(t *testing.T) {
  98. const request = "GET / HTTP/1.0\r\n\r\n"
  99. const response = "the reply that arrives only after the request is complete"
  100. client, a := tcpPair(t)
  101. b, server := tcpPair(t)
  102. go pipeBothWays(a, b)
  103. if _, err := client.Write([]byte(request)); err != nil {
  104. t.Fatalf("client write: %v", err)
  105. }
  106. // The half-close the old relay treated as "tear the whole pair down".
  107. if err := client.(*net.TCPConn).CloseWrite(); err != nil {
  108. t.Fatalf("client CloseWrite: %v", err)
  109. }
  110. _ = server.SetReadDeadline(time.Now().Add(10 * time.Second))
  111. gotReq, err := io.ReadAll(server)
  112. if err != nil {
  113. t.Fatalf("server read: %v", err)
  114. }
  115. if string(gotReq) != request {
  116. t.Fatalf("server got request %q, want %q", gotReq, request)
  117. }
  118. if _, err := server.Write([]byte(response)); err != nil {
  119. t.Fatalf("server write: %v", err)
  120. }
  121. if err := server.(*net.TCPConn).CloseWrite(); err != nil {
  122. t.Fatalf("server CloseWrite: %v", err)
  123. }
  124. _ = client.SetReadDeadline(time.Now().Add(10 * time.Second))
  125. gotResp, err := io.ReadAll(client)
  126. if err != nil {
  127. t.Fatalf("client read: %v", err)
  128. }
  129. if string(gotResp) != response {
  130. t.Fatalf("client got response %q, want %q: the reply was cut off by the half-close", gotResp, response)
  131. }
  132. }
  133. // TestPipeBothWaysClosesWhenBothSidesFinish keeps the teardown contract: both
  134. // directions ending must return, not hang on the idle bound.
  135. func TestPipeBothWaysClosesWhenBothSidesFinish(t *testing.T) {
  136. client, a := tcpPair(t)
  137. b, server := tcpPair(t)
  138. done := make(chan struct{})
  139. go func() { defer close(done); pipeBothWays(a, b) }()
  140. _ = client.(*net.TCPConn).CloseWrite()
  141. _, _ = io.ReadAll(server)
  142. _ = server.(*net.TCPConn).CloseWrite()
  143. _, _ = io.ReadAll(client)
  144. select {
  145. case <-done:
  146. case <-time.After(10 * time.Second):
  147. t.Fatal("pipeBothWays did not return after both directions ended")
  148. }
  149. }
  150. // liveUDPSession returns a session whose udpConn is connected to the returned
  151. // peer, so a test can hand receive() one exact reply datagram.
  152. func liveUDPSession(t *testing.T) (*socks5UDPSession, *net.UDPConn) {
  153. t.Helper()
  154. peer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  155. if err != nil {
  156. t.Fatalf("listen udp: %v", err)
  157. }
  158. t.Cleanup(func() { _ = peer.Close() })
  159. udpConn, err := net.DialUDP("udp", nil, peer.LocalAddr().(*net.UDPAddr))
  160. if err != nil {
  161. t.Fatalf("dial udp: %v", err)
  162. }
  163. t.Cleanup(func() { _ = udpConn.Close() })
  164. return &socks5UDPSession{udpConn: udpConn}, peer
  165. }
  166. // sendReply delivers one raw datagram to sess's socket.
  167. func sendReply(t *testing.T, sess *socks5UDPSession, peer *net.UDPConn, datagram []byte) {
  168. t.Helper()
  169. if _, err := peer.WriteToUDP(datagram, sess.udpConn.LocalAddr().(*net.UDPAddr)); err != nil {
  170. t.Fatalf("write reply: %v", err)
  171. }
  172. _ = sess.udpConn.SetReadDeadline(time.Now().Add(5 * time.Second))
  173. }
  174. // TestSocks5ReceiveDecodesReplyAddressTypes covers all three ATYP forms; the
  175. // domain form used to misread its own length byte and never skip the name.
  176. func TestSocks5ReceiveDecodesReplyAddressTypes(t *testing.T) {
  177. tests := []struct {
  178. name string
  179. addrPart []byte
  180. wantAddr string
  181. }{
  182. {"IPv4", []byte{0x01, 10, 0, 0, 7}, "10.0.0.7"},
  183. {"IPv6", append([]byte{0x04}, netip.MustParseAddr("2001:db8::5").AsSlice()...), "2001:db8::5"},
  184. {"domain holding a literal", append([]byte{0x03, 8}, []byte("10.0.0.9")...), "10.0.0.9"},
  185. }
  186. for _, tt := range tests {
  187. t.Run(tt.name, func(t *testing.T) {
  188. sess, peer := liveUDPSession(t)
  189. payload := []byte("the-actual-datagram-payload")
  190. datagram := append([]byte{0x00, 0x00, 0x00}, tt.addrPart...)
  191. datagram = append(datagram, 0x1f, 0x90) // port 8080
  192. datagram = append(datagram, payload...)
  193. sendReply(t, sess, peer, datagram)
  194. buf := make([]byte, 4096)
  195. from, got, err := sess.receive(buf)
  196. if err != nil {
  197. t.Fatalf("receive: %v", err)
  198. }
  199. want := netip.AddrPortFrom(netip.MustParseAddr(tt.wantAddr), 8080)
  200. if from != want {
  201. t.Errorf("source = %v, want %v", from, want)
  202. }
  203. if string(got) != string(payload) {
  204. t.Errorf("payload = %q, want %q", got, payload)
  205. }
  206. })
  207. }
  208. }
  209. // TestSocks5ReceiveRejectsTruncatedReplies pins that a short datagram is an
  210. // error, not a slice-bounds panic in the relay's own pump goroutine.
  211. func TestSocks5ReceiveRejectsTruncatedReplies(t *testing.T) {
  212. tests := []struct {
  213. name string
  214. datagram []byte
  215. }{
  216. {"header only, IPv4 announced", []byte{0x00, 0x00, 0x00, 0x01}},
  217. {"IPv4 address cut short", []byte{0x00, 0x00, 0x00, 0x01, 10, 0}},
  218. {"IPv6 address cut short", []byte{0x00, 0x00, 0x00, 0x04, 0x20, 0x01}},
  219. {"domain length past the end", []byte{0x00, 0x00, 0x00, 0x03, 40, 'a', 'b'}},
  220. {"address complete but port missing", []byte{0x00, 0x00, 0x00, 0x01, 10, 0, 0, 7}},
  221. {"unsupported address type", []byte{0x00, 0x00, 0x00, 0x09, 1, 2, 3, 4, 0, 80}},
  222. }
  223. for _, tt := range tests {
  224. t.Run(tt.name, func(t *testing.T) {
  225. sess, peer := liveUDPSession(t)
  226. sendReply(t, sess, peer, tt.datagram)
  227. buf := make([]byte, 4096)
  228. if _, _, err := sess.receive(buf); err == nil {
  229. t.Fatal("receive accepted a malformed datagram instead of returning an error")
  230. }
  231. })
  232. }
  233. }
  234. // TestNewSocks5UDPSessionGivesUpOnSilentServer pins that a control connection
  235. // the kernel accepts but nobody answers returns within the associate deadline
  236. // instead of parking Handle -- and with it the tunnel's delivery path -- forever.
  237. func TestNewSocks5UDPSessionGivesUpOnSilentServer(t *testing.T) {
  238. // Never accepted: the backlog completes the TCP handshake, the greeting
  239. // lands in the socket buffer, and no reply ever comes -- a hung Xray.
  240. ln, err := net.Listen("tcp", "127.0.0.1:0")
  241. if err != nil {
  242. t.Fatalf("listen: %v", err)
  243. }
  244. t.Cleanup(func() { _ = ln.Close() })
  245. saved := socks5AssociateTimeout
  246. socks5AssociateTimeout = 150 * time.Millisecond
  247. t.Cleanup(func() { socks5AssociateTimeout = saved })
  248. type result struct {
  249. sess *socks5UDPSession
  250. err error
  251. }
  252. done := make(chan result, 1)
  253. go func() {
  254. sess, err := newSocks5UDPSession(ln.Addr().String(), "peer@example", "x")
  255. done <- result{sess, err}
  256. }()
  257. select {
  258. case got := <-done:
  259. if got.err == nil {
  260. got.sess.Close()
  261. t.Fatal("associate succeeded against a server that never answered")
  262. }
  263. if !errors.Is(got.err, os.ErrDeadlineExceeded) {
  264. t.Fatalf("associate error = %v, want one wrapping os.ErrDeadlineExceeded", got.err)
  265. }
  266. case <-time.After(2 * time.Second):
  267. t.Fatal("newSocks5UDPSession still blocked 2s past the associate deadline: a silent SOCKS5 server parks the tunnel's delivery path")
  268. }
  269. }