portfwd_udp.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package amneziawgnet
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "net/netip"
  7. "sync"
  8. "time"
  9. "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
  10. "gvisor.dev/gvisor/pkg/tcpip/stack"
  11. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  12. )
  13. // portForwardUDPIdleTimeout matches UDPRelay.pump's own idle window
  14. // (relay.go) -- both are "how long to keep a per-flow session alive with no
  15. // traffic before tearing it down," so there's no reason for the two
  16. // directions to disagree.
  17. const portForwardUDPIdleTimeout = 2 * time.Minute
  18. // udpForwardSession is one established flow from a single external source
  19. // address into the tunnel toward a peer -- conn is a connected gonet UDP
  20. // endpoint (DialUDP with a non-nil raddr), so plain Read/Write, not
  21. // ReadFrom/WriteTo, address it correctly.
  22. type udpForwardSession struct {
  23. conn *gonet.UDPConn
  24. }
  25. // udpForwardListener is one open host-facing UDP socket for a single
  26. // portForwardKey, demultiplexing by external source address -- the mirror
  27. // image of AttachUDPHandler/UDPRelay, which demultiplex by tunnel-internal
  28. // source for the opposite direction. net.ListenPacket has no accept/session
  29. // model of its own, so this package tracks sessions itself here, the same
  30. // way UDPRelay already does in relay.go.
  31. type udpForwardListener struct {
  32. pc net.PacketConn
  33. mu sync.Mutex
  34. sessions map[netip.AddrPort]*udpForwardSession
  35. }
  36. // listenPortForwardUDP opens a host-facing UDP socket on key.port and
  37. // starts demultiplexing datagrams into per-source-address tunnel sessions
  38. // toward target(key.email). Bind-failure contract matches
  39. // listenPortForwardTCP exactly: log, return nil, Reconcile retries later.
  40. func listenPortForwardUDP(gstack *stack.Stack, inboundID int, key portForwardKey, target portForwardTargetFunc) *udpForwardListener {
  41. pc, err := (&net.ListenConfig{}).ListenPacket(context.Background(), "udp", fmt.Sprintf(":%d", key.port))
  42. if err != nil {
  43. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: listen udp :%d: %v", inboundID, key.email, key.port, err)
  44. return nil
  45. }
  46. l := &udpForwardListener{pc: pc, sessions: map[netip.AddrPort]*udpForwardSession{}}
  47. logger.Infof("amneziawgnet: port-forward: inbound %d peer %q: listening udp :%d", inboundID, key.email, key.port)
  48. go l.readLoop(gstack, inboundID, key, target)
  49. return l
  50. }
  51. func (l *udpForwardListener) readLoop(gstack *stack.Stack, inboundID int, key portForwardKey, target portForwardTargetFunc) {
  52. buf := make([]byte, 65536)
  53. for {
  54. n, from, err := l.pc.ReadFrom(buf)
  55. if err != nil {
  56. return // closed
  57. }
  58. src, ok := udpAddrPort(from)
  59. if !ok {
  60. continue
  61. }
  62. l.mu.Lock()
  63. sess, exists := l.sessions[src]
  64. l.mu.Unlock()
  65. if !exists {
  66. addr, ok := target(key.email)
  67. if !ok {
  68. continue
  69. }
  70. raddr := tunnelFullAddress(addr, key.port)
  71. conn, err := gonet.DialUDP(gstack, nil, &raddr, tunnelNetwork(addr))
  72. if err != nil {
  73. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: dial tunnel %s:%d: %v", inboundID, key.email, addr, key.port, err)
  74. continue
  75. }
  76. sess = &udpForwardSession{conn: conn}
  77. l.mu.Lock()
  78. l.sessions[src] = sess
  79. l.mu.Unlock()
  80. go l.pump(src, sess)
  81. }
  82. // buf is reused by the next ReadFrom the instant this loop continues,
  83. // so the session's own goroutine can't be handed a slice into it --
  84. // Write copies synchronously here, on this goroutine, before that
  85. // can happen, so no copy of the payload is needed.
  86. if _, err := sess.conn.Write(buf[:n]); err != nil {
  87. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: write tunnel: %v", inboundID, key.email, err)
  88. }
  89. }
  90. }
  91. // pump reads replies from sess and writes them back to the external source
  92. // src until the session errors out or goes idle, mirroring UDPRelay.pump's
  93. // exact structure (relay.go) for the opposite direction.
  94. func (l *udpForwardListener) pump(src netip.AddrPort, sess *udpForwardSession) {
  95. defer func() {
  96. l.mu.Lock()
  97. delete(l.sessions, src)
  98. l.mu.Unlock()
  99. sess.conn.Close()
  100. }()
  101. buf := make([]byte, 65536)
  102. for {
  103. _ = sess.conn.SetReadDeadline(time.Now().Add(portForwardUDPIdleTimeout))
  104. n, err := sess.conn.Read(buf)
  105. if err != nil {
  106. return
  107. }
  108. if _, err := l.pc.WriteTo(buf[:n], net.UDPAddrFromAddrPort(src)); err != nil {
  109. return
  110. }
  111. }
  112. }
  113. // Close tears down every open session and the underlying socket.
  114. func (l *udpForwardListener) Close() {
  115. l.mu.Lock()
  116. sessions := l.sessions
  117. l.sessions = map[netip.AddrPort]*udpForwardSession{}
  118. l.mu.Unlock()
  119. for _, sess := range sessions {
  120. sess.conn.Close()
  121. }
  122. l.pc.Close()
  123. }
  124. // udpAddrPort extracts a netip.AddrPort from a net.Addr returned by
  125. // net.ListenPacket's ReadFrom -- always a *net.UDPAddr in practice for a
  126. // "udp" network listener, but handled defensively rather than assumed.
  127. func udpAddrPort(addr net.Addr) (netip.AddrPort, bool) {
  128. udpAddr, ok := addr.(*net.UDPAddr)
  129. if !ok {
  130. return netip.AddrPort{}, false
  131. }
  132. return udpAddr.AddrPort(), true
  133. }