udp_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package amneziawgnet
  2. import (
  3. "fmt"
  4. "net/netip"
  5. "testing"
  6. "time"
  7. awgconn "github.com/amnezia-vpn/amneziawg-go/v3/conn"
  8. "github.com/amnezia-vpn/amneziawg-go/v3/device"
  9. "github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
  10. "gvisor.dev/gvisor/pkg/tcpip"
  11. "gvisor.dev/gvisor/pkg/tcpip/header"
  12. "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
  13. "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  14. )
  15. // TestNewDeviceUDPHandlerAndReply is the UDP counterpart of
  16. // TestNewDeviceHandshakeForwarderAndIdentity: this package's own udp.go was
  17. // refactored from the Phase 0 spike's bake-the-dial-in version to a generic
  18. // handler-plus-reply-injection design (see AttachUDPHandler/WriteUDPReply's
  19. // doc comments), a real behavior change worth its own verification rather
  20. // than assuming the port preserved correctness -- UDP was flagged as "the
  21. // harder half" in the migration plan's own risk list, precisely because
  22. // gVisor has no udp.NewForwarder and the reply path has to be constructed
  23. // by hand.
  24. func TestNewDeviceUDPHandlerAndReply(t *testing.T) {
  25. serverPriv, serverPub, err := wireguard.GenerateWireguardKeypair()
  26. if err != nil {
  27. t.Fatalf("generate server keypair: %v", err)
  28. }
  29. clientPriv, clientPub, err := wireguard.GenerateWireguardKeypair()
  30. if err != nil {
  31. t.Fatalf("generate client keypair: %v", err)
  32. }
  33. const listenPort = 58713 // distinct from the TCP test's port
  34. const wantEmail = "[email protected]"
  35. const echoPayload = "hello-from-client"
  36. inst := amneziawg.Instance{
  37. Id: 2,
  38. InterfaceName: "awgtest2",
  39. ListenPort: listenPort,
  40. PrivateKey: serverPriv,
  41. PublicKey: serverPub,
  42. Address: []string{"10.202.0.1/24"},
  43. MTU: 1420,
  44. Obfuscation: amneziawg.Obfuscation31{
  45. Jc: 4, Jmin: 40, Jmax: 70,
  46. S1: 20, S2: 30, S3: 20, S4: 20,
  47. },
  48. Peers: []amneziawg.Peer{{
  49. Email: wantEmail,
  50. PublicKey: clientPub,
  51. AllowedIPs: []string{"10.202.0.2/32"},
  52. }},
  53. }
  54. dev, err := newUnconfiguredDevice(inst, DeviceOptions{})
  55. if err != nil {
  56. t.Fatalf("newUnconfiguredDevice: %v", err)
  57. }
  58. defer dev.Close()
  59. idx := NewPeerIndex(inst.Peers)
  60. // Never configured anywhere server-side, same idea as the TCP test.
  61. wantDest := netip.MustParseAddrPort("10.202.9.9:5353")
  62. identityErrCh := make(chan error, 8)
  63. AttachUDPHandler(dev.Stack, func(src, dst netip.AddrPort, payload []byte) {
  64. if peer, ok := idx.Lookup(src.Addr()); !ok || peer.Email != wantEmail {
  65. identityErrCh <- fmt.Errorf("peer identity lookup for src %v: ok=%v email=%q, want %q", src, ok, peer.Email, wantEmail)
  66. return
  67. }
  68. if dst != wantDest {
  69. identityErrCh <- fmt.Errorf("recovered dest = %v, want %v", dst, wantDest)
  70. return
  71. }
  72. // Echo the payload back, posing as a reply from the destination the
  73. // client dialed -- exactly what a real relay's downstream reply
  74. // would look like from the tunnel's point of view.
  75. if err := WriteUDPReply(dev.Stack, dst, src, payload); err != nil {
  76. identityErrCh <- fmt.Errorf("WriteUDPReply: %w", err)
  77. }
  78. })
  79. // Configure (IpcSet) must come after AttachUDPHandler -- see
  80. // newUnconfiguredDevice's doc comment.
  81. if err := dev.Configure(inst, DeviceOptions{}); err != nil {
  82. t.Fatalf("Configure: %v", err)
  83. }
  84. clientTun, clientNet, err := netstack.CreateNetTUN(
  85. []netip.Addr{netip.MustParseAddr("10.202.0.2")},
  86. []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 1420)
  87. if err != nil {
  88. t.Fatalf("client CreateNetTUN: %v", err)
  89. }
  90. clientDev := device.NewDevice(clientTun, awgconn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, ""))
  91. defer clientDev.Close()
  92. clientPrivHex, err := wireguard.KeyToHex(clientPriv)
  93. if err != nil {
  94. t.Fatalf("client key to hex: %v", err)
  95. }
  96. serverPubHex, err := wireguard.KeyToHex(serverPub)
  97. if err != nil {
  98. t.Fatalf("server key to hex: %v", err)
  99. }
  100. clientConf := fmt.Sprintf(
  101. "private_key=%s\njc=4\njmin=40\njmax=70\ns1=20\ns2=30\ns3=20\ns4=20\npublic_key=%s\nendpoint=127.0.0.1:%d\nallowed_ip=0.0.0.0/0\n",
  102. clientPrivHex, serverPubHex, listenPort)
  103. if err := clientDev.IpcSet(clientConf); err != nil {
  104. t.Fatalf("client IpcSet: %v", err)
  105. }
  106. if err := clientDev.Up(); err != nil {
  107. t.Fatalf("client Up: %v", err)
  108. }
  109. conn, err := clientNet.DialUDPAddrPort(netip.AddrPort{}, wantDest)
  110. if err != nil {
  111. t.Fatalf("client DialUDPAddrPort: %v", err)
  112. }
  113. defer conn.Close()
  114. deadline := time.Now().Add(5 * time.Second)
  115. var buf [256]byte
  116. for {
  117. select {
  118. case err := <-identityErrCh:
  119. t.Fatal(err)
  120. default:
  121. }
  122. _ = conn.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
  123. if _, err := conn.Write([]byte(echoPayload)); err != nil {
  124. if time.Now().After(deadline) {
  125. t.Fatalf("client write never succeeded: %v", err)
  126. }
  127. continue
  128. }
  129. _ = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
  130. n, err := conn.Read(buf[:])
  131. if err != nil {
  132. if time.Now().After(deadline) {
  133. t.Fatalf("client never received a reply: %v", err)
  134. }
  135. continue
  136. }
  137. if got := string(buf[:n]); got != echoPayload {
  138. t.Fatalf("echoed payload = %q, want %q", got, echoPayload)
  139. }
  140. return
  141. }
  142. }
  143. // udpDatagram builds a complete IPv4/UDP packet, the shape stackTun.Write
  144. // expects from amneziawg-go after decryption.
  145. func udpDatagram(src, dst netip.AddrPort, payload []byte) []byte {
  146. total := header.IPv4MinimumSize + header.UDPMinimumSize + len(payload)
  147. p := make([]byte, total)
  148. ip := header.IPv4(p)
  149. ip.Encode(&header.IPv4Fields{
  150. TotalLength: uint16(total),
  151. TTL: 64,
  152. Protocol: uint8(header.UDPProtocolNumber),
  153. SrcAddr: tcpip.AddrFromSlice(src.Addr().AsSlice()),
  154. DstAddr: tcpip.AddrFromSlice(dst.Addr().AsSlice()),
  155. })
  156. ip.SetChecksum(^ip.CalculateChecksum())
  157. u := header.UDP(p[header.IPv4MinimumSize:])
  158. u.Encode(&header.UDPFields{
  159. SrcPort: src.Port(),
  160. DstPort: dst.Port(),
  161. Length: uint16(header.UDPMinimumSize + len(payload)),
  162. })
  163. copy(p[header.IPv4MinimumSize+header.UDPMinimumSize:], payload)
  164. return p
  165. }
  166. // TestAttachUDPHandlerDoesNotStrandPacketBuffers drives a real datagram all the
  167. // way through the stack: Range.ToSlice already copies, so cloning pkt only leaks.
  168. func TestAttachUDPHandlerDoesNotStrandPacketBuffers(t *testing.T) {
  169. tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("10.77.0.1")}, 1420)
  170. if err != nil {
  171. t.Fatalf("createNetTUNWithStack: %v", err)
  172. }
  173. defer tun.Close()
  174. src := netip.MustParseAddrPort("10.77.0.2:40000")
  175. dst := netip.MustParseAddrPort("10.77.9.9:5353")
  176. payload := make([]byte, 512)
  177. var delivered int
  178. AttachUDPHandler(gstack, func(gotSrc, gotDst netip.AddrPort, got []byte) {
  179. if gotSrc != src || gotDst != dst || len(got) != len(payload) {
  180. t.Errorf("handler got (%v -> %v, %d bytes), want (%v -> %v, %d bytes)", gotSrc, gotDst, len(got), src, dst, len(payload))
  181. }
  182. delivered++
  183. })
  184. bufs := [][]byte{udpDatagram(src, dst, payload)}
  185. st := tun.(*stackTun)
  186. allocs := testing.AllocsPerRun(500, func() {
  187. if _, err := st.Write(bufs, 0); err != nil {
  188. t.Fatalf("Write: %v", err)
  189. }
  190. })
  191. if delivered == 0 {
  192. t.Fatal("handler never ran: the datagram never reached the UDP transport handler")
  193. }
  194. // 2 once nothing is stranded (1 is ToSlice itself), 8 with the leaked
  195. // clone plus the un-released packet buffer; -race adds ~1.
  196. if allocs > 4 {
  197. t.Fatalf("UDP delivery allocates %v times per datagram, want <=4: pooled packet buffers are being stranded", allocs)
  198. }
  199. }