0e96f602fa
matcher: fix IPv6 normalization via netip.ParseAddr, port-range overwrite by switching to []*PortRange map, case-insensitive domain/host matching, glob.MustCompile→Compile to avoid panic on invalid patterns. plugin: guard nil *Options in NewGRPCConn and NewHTTPClient; add HTTPClientTransport helper for idle-connection cleanup. net/*: add doc comments for all exported symbols (no code changes). Add 30 tests (matcher_test.go 22, plugin_test.go 8) covering all matcher types, nil safety, case insensitivity, IPv6 normalization, port ranges, and plugin option composition.
143 lines
2.7 KiB
Go
143 lines
2.7 KiB
Go
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
|
|
}
|