fix(matcher,plugin): case-insensitive matching, port accumulation, nil guards, doc comments
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.
This commit is contained in:
@@ -8,6 +8,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseInterfaceAddr resolves ifceName to a network interface name and its addresses.
|
||||
// If ifceName is an IP address, it finds the corresponding interface and returns
|
||||
// a single address with port 0. If ifceName is an interface name, it returns all
|
||||
// addresses assigned to that interface. The network parameter determines the address
|
||||
// type (TCPAddr for "tcp"/"tcp4"/"tcp6", UDPAddr for "udp"/"udp4"/"udp6", IPAddr
|
||||
// otherwise).
|
||||
func ParseInterfaceAddr(ifceName, network string) (ifce string, addr []net.Addr, err error) {
|
||||
if ifceName == "" {
|
||||
addr = append(addr, nil)
|
||||
@@ -90,6 +96,7 @@ func findInterfaceByIP(ip net.IP) (string, error) {
|
||||
// e.g. 192.168.1.1:0-65535
|
||||
type AddrPortRange string
|
||||
|
||||
// Addrs expands the port range into individual "host:port" strings.
|
||||
func (p AddrPortRange) Addrs() (addrs []string) {
|
||||
// ignore url scheme, e.g. http://, tls://, tcp://.
|
||||
if strings.Contains(string(p), "://") {
|
||||
@@ -151,15 +158,18 @@ func (pr *PortRange) Parse(s string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Contains reports whether port falls within the range.
|
||||
func (pr *PortRange) Contains(port int) bool {
|
||||
return port >= pr.Min && port <= pr.Max
|
||||
}
|
||||
|
||||
// IPRange represents a range of IP addresses.
|
||||
type IPRange struct {
|
||||
Min netip.Addr
|
||||
Max netip.Addr
|
||||
}
|
||||
|
||||
// Parse parses s as a single IP or IP range "min-max" into r.
|
||||
func (r *IPRange) Parse(s string) error {
|
||||
minmax := strings.Split(s, "-")
|
||||
switch len(minmax) {
|
||||
@@ -190,6 +200,7 @@ func (r *IPRange) Parse(s string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Contains reports whether addr falls within the IP range.
|
||||
func (r *IPRange) Contains(addr netip.Addr) bool {
|
||||
return !(addr.Less(r.Min) || r.Max.Less(addr))
|
||||
}
|
||||
|
||||
@@ -16,13 +16,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTimeout is the default dial timeout.
|
||||
DefaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// DefaultNetDialer is the default Dialer used when a nil Dialer is provided.
|
||||
var (
|
||||
DefaultNetDialer = &Dialer{}
|
||||
)
|
||||
|
||||
// Dialer is a network dialer with support for interface binding, network
|
||||
// namespace switching, and socket marking. The zero value is ready to use
|
||||
// via DefaultNetDialer.
|
||||
type Dialer struct {
|
||||
Interface string
|
||||
Netns string
|
||||
@@ -31,6 +36,10 @@ type Dialer struct {
|
||||
Log logger.Logger
|
||||
}
|
||||
|
||||
// Dial connects to addr on the named network. If d is nil, DefaultNetDialer
|
||||
// is used. When Netns is set, the dial switches into that network namespace
|
||||
// before creating the connection. When Interface is set, it iterates the
|
||||
// specified interfaces, trying each address in turn.
|
||||
func (d *Dialer) Dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
if d == nil {
|
||||
d = DefaultNetDialer
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetClientIP extracts the client IP from the request, checking CF-Connecting-IP,
|
||||
// X-Forwarded-For, and X-Real-Ip headers in that order.
|
||||
func GetClientIP(req *http.Request) net.IP {
|
||||
if req == nil {
|
||||
return nil
|
||||
@@ -27,6 +29,7 @@ func GetClientIP(req *http.Request) net.IP {
|
||||
return net.ParseIP(sip)
|
||||
}
|
||||
|
||||
// Body wraps an io.ReadCloser with size-limited recording of the data read.
|
||||
type Body struct {
|
||||
r io.ReadCloser
|
||||
buf bytes.Buffer
|
||||
@@ -34,6 +37,7 @@ type Body struct {
|
||||
recordSize int
|
||||
}
|
||||
|
||||
// NewBody creates a Body that reads from r and records up to maxRecordSize bytes.
|
||||
func NewBody(r io.ReadCloser, maxRecordSize int) *Body {
|
||||
p := &Body{
|
||||
r: r,
|
||||
@@ -63,10 +67,12 @@ func (p *Body) Close() error {
|
||||
return p.r.Close()
|
||||
}
|
||||
|
||||
// Content returns the recorded content.
|
||||
func (p *Body) Content() []byte {
|
||||
return p.buf.Bytes()
|
||||
}
|
||||
|
||||
// Length returns the total number of bytes read from the underlying reader.
|
||||
func (p *Body) Length() int64 {
|
||||
return p.length
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ var prots = map[waterutil.IPProtocol]string{
|
||||
waterutil.IPv6_ICMP: "IPv6-ICMP",
|
||||
}
|
||||
|
||||
// Protocol returns the string name for the given IP protocol number.
|
||||
func Protocol(p waterutil.IPProtocol) string {
|
||||
if v, ok := prots[p]; ok {
|
||||
return v
|
||||
|
||||
+14
-1
@@ -13,33 +13,41 @@ import (
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
// SetBuffer is a connection that supports setting send and receive buffer sizes.
|
||||
type SetBuffer interface {
|
||||
SetReadBuffer(bytes int) error
|
||||
SetWriteBuffer(bytes int) error
|
||||
}
|
||||
|
||||
// SyscallConn is a connection that exposes its raw system file descriptor.
|
||||
type SyscallConn interface {
|
||||
SyscallConn() (syscall.RawConn, error)
|
||||
}
|
||||
|
||||
// RemoteAddr is a connection that exposes its remote address.
|
||||
type RemoteAddr interface {
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
// tcpraw.TCPConn
|
||||
// SetDSCP is a connection that supports setting the DSCP field.
|
||||
type SetDSCP interface {
|
||||
SetDSCP(int) error
|
||||
}
|
||||
|
||||
// IsIPv4 reports whether address is an IPv4 address by checking whether
|
||||
// it starts with ':' or '[' (IPv6) vs a digit (IPv4).
|
||||
func IsIPv4(address string) bool {
|
||||
return address != "" && address[0] != ':' && address[0] != '['
|
||||
}
|
||||
|
||||
// ListenConfig extends net.ListenConfig with network namespace support.
|
||||
type ListenConfig struct {
|
||||
Netns string
|
||||
net.ListenConfig
|
||||
}
|
||||
|
||||
// Listen announces on the local network address, switching into the configured
|
||||
// network namespace first if one is set.
|
||||
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
if lc.Netns != "" {
|
||||
runtime.LockOSThread()
|
||||
@@ -70,6 +78,8 @@ func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (ne
|
||||
return lc.ListenConfig.Listen(ctx, network, address)
|
||||
}
|
||||
|
||||
// ListenPacket announces on the local network address for packet connections,
|
||||
// switching into the configured network namespace first if one is set.
|
||||
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
if lc.Netns != "" {
|
||||
runtime.LockOSThread()
|
||||
@@ -105,6 +115,9 @@ type readWriteConn struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewReadWriteConn returns a net.Conn that reads from r and writes to w,
|
||||
// delegating all other operations to c. If c supports CloseRead or CloseWrite,
|
||||
// those are forwarded; otherwise they return ErrUnsupported.
|
||||
func NewReadWriteConn(r io.Reader, w io.Writer, c net.Conn) net.Conn {
|
||||
return &readWriteConn{
|
||||
Conn: c,
|
||||
|
||||
@@ -31,7 +31,11 @@ func WithReadTimeout(d time.Duration) PipeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// Pipe 在两个连接之间建立双向数据通道
|
||||
// Pipe establishes a bidirectional data channel between two connections.
|
||||
// Data read from rw1 is written to rw2, and data read from rw2 is written to
|
||||
// rw1. Each direction runs in its own goroutine. Pipe returns the first
|
||||
// non-EOF error encountered, or nil if both directions complete cleanly.
|
||||
// When ctx is cancelled, both connections are forcefully closed.
|
||||
func Pipe(ctx context.Context, rw1, rw2 io.ReadWriteCloser, opts ...PipeOption) error {
|
||||
var options pipeOptions
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -56,6 +56,9 @@ func (c *serverConn) CloseWrite() error {
|
||||
return xio.ErrUnsupported
|
||||
}
|
||||
|
||||
// WrapClientConn writes a PROXY protocol header to c using src and dst
|
||||
// addresses, then returns c for continued use. If ppv is <= 0 or either
|
||||
// address is nil, the connection is returned unchanged.
|
||||
func WrapClientConn(ppv int, src, dst net.Addr, c net.Conn) net.Conn {
|
||||
if ppv <= 0 || c == nil {
|
||||
return c
|
||||
|
||||
@@ -32,6 +32,8 @@ func (ln *listener) Accept() (net.Conn, error) {
|
||||
return &serverConn{Conn: conn, ctx: innerCtx}, nil
|
||||
}
|
||||
|
||||
// WrapListener wraps ln with a PROXY protocol listener that parses the PROXY
|
||||
// header on each accepted connection. If ppv <= 0, ln is returned unchanged.
|
||||
func WrapListener(ppv int, ln net.Listener, readHeaderTimeout time.Duration) net.Listener {
|
||||
if ppv <= 0 {
|
||||
return ln
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
)
|
||||
|
||||
// Resolve resolves addr to a concrete IP address. If hosts is non-nil, it is
|
||||
// consulted first. If r is non-nil and no host mapping matched, the resolver
|
||||
// is used. If neither is available, addr is returned unchanged.
|
||||
func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) {
|
||||
if addr == "" {
|
||||
return addr, nil
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
bufferSize = 64 * 1024
|
||||
)
|
||||
|
||||
// Transport copies data bidirectionally between rw1 and rw2 using two
|
||||
// goroutines. It returns the first non-EOF error encountered.
|
||||
func Transport(rw1, rw2 io.ReadWriter) error {
|
||||
errc := make(chan error, 2)
|
||||
go func() {
|
||||
@@ -27,6 +29,8 @@ func Transport(rw1, rw2 io.ReadWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyBuffer copies from src to dst using a buffer of bufSize obtained from
|
||||
// the buffer pool, reducing allocation overhead for repeated copies.
|
||||
func CopyBuffer(dst io.Writer, src io.Reader, bufSize int) error {
|
||||
buf := bufpool.Get(bufSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
)
|
||||
|
||||
// Conn is a net.PacketConn that also supports io.Reader, io.Writer, buffer
|
||||
// sizing, syscall access, and remote address exposure.
|
||||
type Conn interface {
|
||||
net.PacketConn
|
||||
io.Reader
|
||||
@@ -18,11 +20,13 @@ type Conn interface {
|
||||
xnet.RemoteAddr
|
||||
}
|
||||
|
||||
// ReadUDP supports reading UDP datagrams with metadata.
|
||||
type ReadUDP interface {
|
||||
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
|
||||
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
|
||||
}
|
||||
|
||||
// WriteUDP supports writing UDP datagrams with metadata.
|
||||
type WriteUDP interface {
|
||||
WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
|
||||
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/go-gost/core/logger"
|
||||
)
|
||||
|
||||
// ListenConfig configures a UDP-to-TCP-stream listener.
|
||||
type ListenConfig struct {
|
||||
Addr net.Addr
|
||||
Backlog int
|
||||
@@ -29,6 +30,9 @@ type listener struct {
|
||||
config *ListenConfig
|
||||
}
|
||||
|
||||
// NewListener creates a net.Listener from a net.PacketConn by demultiplexing
|
||||
// UDP datagrams into per-client net.Conn streams. Idle connections are cleaned
|
||||
// up according to cfg.TTL.
|
||||
func NewListener(conn net.PacketConn, cfg *ListenConfig) net.Listener {
|
||||
if cfg == nil {
|
||||
cfg = &ListenConfig{}
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
defaultBufferSize = 4096
|
||||
)
|
||||
|
||||
// Relay copies UDP datagrams between two net.PacketConns bidirectionally.
|
||||
type Relay struct {
|
||||
service string
|
||||
pc1 net.PacketConn
|
||||
@@ -22,6 +23,7 @@ type Relay struct {
|
||||
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,
|
||||
@@ -29,26 +31,31 @@ func NewRelay(pc1, pc2 net.PacketConn) *Relay {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user