package udp import ( "context" "net" "github.com/go-gost/core/bypass" "github.com/go-gost/core/common/bufpool" "github.com/go-gost/core/logger" ) const ( defaultBufferSize = 4096 ) // Relay copies UDP datagrams between two net.PacketConns bidirectionally. type Relay struct { service string pc1 net.PacketConn pc2 net.PacketConn bufferSize int bypass bypass.Bypass logger logger.Logger } // NewRelay creates a Relay that copies datagrams between pc1 and pc2. func NewRelay(pc1, pc2 net.PacketConn) *Relay { return &Relay{ pc1: pc1, pc2: pc2, } } // WithService sets the service name for bypass matching. func (r *Relay) WithService(service string) *Relay { r.service = service return r } // WithBypass sets the bypass matcher to skip certain addresses. func (r *Relay) WithBypass(bp bypass.Bypass) *Relay { r.bypass = bp return r } // WithLogger sets the logger. func (r *Relay) WithLogger(logger logger.Logger) *Relay { r.logger = logger return r } // WithBufferSize sets the buffer size for copy operations. func (r *Relay) WithBufferSize(n int) *Relay { r.bufferSize = n return r } // Run starts the relay. It blocks until an error occurs or ctx is cancelled. func (r *Relay) Run(ctx context.Context) (err error) { errc := make(chan error, 2) bufferSize := r.bufferSize if bufferSize <= 0 { bufferSize = defaultBufferSize } go func() { b := bufpool.Get(bufferSize) defer bufpool.Put(b) for { err := func() error { n, raddr, err := r.pc1.ReadFrom(b) if err != nil { return err } if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String(), bypass.WithService(r.service)) { if r.logger != nil { r.logger.Warn("bypass: ", raddr) } return nil } if _, err := r.pc2.WriteTo(b[:n], raddr); err != nil { return err } if r.logger != nil { r.logger.Tracef("%s >>> %s data: %d", r.pc2.LocalAddr(), raddr, n) } return nil }() if err != nil { errc <- err return } } }() go func() { b := bufpool.Get(bufferSize) defer bufpool.Put(b) for { err := func() error { n, raddr, err := r.pc2.ReadFrom(b) if err != nil { return err } if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String(), bypass.WithService(r.service)) { if r.logger != nil { r.logger.Warn("bypass: ", raddr) } return nil } if _, err := r.pc1.WriteTo(b[:n], raddr); err != nil { return err } if r.logger != nil { r.logger.Tracef("%s <<< %s data: %d", r.pc2.LocalAddr(), raddr, n) } return nil }() if err != nil { errc <- err return } } }() return <-errc }