Documentation
¶
Overview ¶
Package socks5 implements a production-grade SOCKS5 server and client per RFC 1928, with RFC 1929 username/password authentication. It supports the CONNECT, BIND, and UDP ASSOCIATE commands and ships with pluggable Authenticator, RuleSet, NameResolver, Forwarder and Observer interfaces plus middleware chains for per-command extensibility.
GSSAPI authentication (RFC 1961, method 0x01) is intentionally out of scope; see AGENTS.md for the package's design principles. (Doc-02)
Index ¶
- Constants
- Variables
- func DNSResolver(ctx context.Context, name string) (context.Context, []net.IP, error)
- func DefaultForwarder(src, dst net.Conn) error
- func LiteForwarder(src, dst io.ReadWriter) error
- func MeteredForwarder(m Metrics) func(src, dst net.Conn) error
- func NewLogger(l zerolog.Logger) *zerolog.Logger
- func NewStdLogger() *zerolog.Logger
- func PermitAll(ctx context.Context, req *Request) (context.Context, bool)
- func PermitCommand(connect, bind, assoc bool) func(ctx context.Context, req *Request) (context.Context, bool)
- func PermitDest(cidrs ...string) func(ctx context.Context, req *Request) (context.Context, bool)
- func PermitNone(ctx context.Context, req *Request) (context.Context, bool)
- func PermitPorts(ports ...int) func(ctx context.Context, req *Request) (context.Context, bool)
- func PermitSource(cidrs ...string) func(ctx context.Context, req *Request) (context.Context, bool)
- func ProxyStream(a, b net.Conn) error
- func ReadMethods(r io.Reader) ([]byte, error)
- func SendReply(w io.Writer, rep uint8, bindAddr net.Addr) error
- type AddrSpec
- type AuthContext
- type Authenticator
- type BoundConn
- type BytesPool
- type Client
- func (c *Client) Associate(ctx context.Context, targetAddr string) (*UDPRelay, error)
- func (c *Client) Bind(ctx context.Context, targetAddr string) (*BoundConn, error)
- func (c *Client) Dial(network, targetAddr string) (net.Conn, error)
- func (c *Client) DialContext(ctx context.Context, network, targetAddr string) (net.Conn, error)
- type ClientConfig
- type CloseReader
- type CloseWriter
- type CredentialStore
- type Datagram
- type Handler
- type Listener
- type MethodReply
- type MethodRequest
- type Metrics
- type Middleware
- type MiddlewareChain
- type NoAuthAuthenticator
- type NoOpMetrics
- func (NoOpMetrics) AddBytesProxied(_ context.Context, _ string, _ int64)
- func (NoOpMetrics) IncAuthFailure(_ context.Context, _ uint8)
- func (NoOpMetrics) IncCommand(_ context.Context, _ uint8)
- func (NoOpMetrics) IncConnectionsAccepted(_ context.Context)
- func (NoOpMetrics) IncConnectionsRejected(_ context.Context, _ string)
- type NoOpObserver
- func (NoOpObserver) OnAssociate(_ context.Context, _ *Request, _ net.PacketConn)
- func (NoOpObserver) OnBind(_ context.Context, _ *Request, _ net.Listener)
- func (NoOpObserver) OnConnect(_ context.Context, _ *Request, _ net.Conn)
- func (NoOpObserver) OnError(_ context.Context, _ *Request, _ error)
- func (NoOpObserver) OnShutdown()
- type Observer
- type PacketConn
- func (vpc *PacketConn) Close() error
- func (vpc *PacketConn) LocalAddr() net.Addr
- func (vpc *PacketConn) ReadFrom(p []byte) (int, net.Addr, error)
- func (vpc *PacketConn) SetDeadline(t time.Time) error
- func (vpc *PacketConn) SetReadDeadline(t time.Time) error
- func (vpc *PacketConn) SetWriteDeadline(t time.Time) error
- func (vpc *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error)
- type ProtoReply
- type ProtoRequest
- type ReplyError
- type Request
- type RoutinePool
- type Server
- type ServerConfig
- type SocksConn
- type StaticCredentials
- type UDPRelay
- type UserPassAuthenticator
- type UserPassReply
- type UserPassRequest
Constants ¶
const ( CommandConnect = 1 CommandBind = 2 CommandAssociate = 3 )
Command codes as defined in RFC 1928, section 4.
const ( AddressIPv4 = 1 AddressDomainName = 3 AddressIPv6 = 4 )
Address type constants as defined in RFC 1928, section 5.
const ( ReplySucceeded = 0 ReplyGeneralFailure = 1 ReplyConnectionNotAllowed = 2 ReplyNetworkUnreachable = 3 ReplyHostUnreachable = 4 ReplyConnectionRefused = 5 ReplyTTLExpired = 6 ReplyCommandNotSupported = 7 ReplyAddrTypeNotSupported = 8 )
SOCKS reply codes as defined in RFC 1928, section 6.
const ( AuthMethodNoAcceptable = 0xFF AuthMethodNoAuth = 0 AuthMethodUserPass = 2 )
Authentication method constants.
Variables ¶
var ( ErrUnrecognizedAddrType = errors.New("socks5: unrecognized address type") ErrNoSupportedAuth = errors.New("socks5: no supported authentication mechanism") ErrUserAuthFailed = errors.New("socks5: user authentication failed") ErrInvalidVersion = errors.New("socks5: invalid SOCKS version") ErrInvalidRequest = errors.New("socks5: invalid request") ErrCommandNotSupported = errors.New("socks5: command not supported") ErrConnectionNotAllowed = errors.New("socks5: connection not allowed") ErrInvalidHostname = errors.New("socks5: invalid hostname") ErrHostUnreachable = errors.New("socks5: host unreachable") ErrShuttingDown = errors.New("socks5: server is shutting down") ErrReservedNotZero = errors.New("socks5: reserved byte is non-zero") ErrServerAlreadyServing = errors.New("socks5: Server.Serve already called") )
SOCKS errors.
var ErrListenerNotFound = errors.New("socks5: virtual listener not found")
ErrListenerNotFound is returned when a virtual listener is not found for the requested address.
var ErrProxyProtoMalformed = errors.New("socks5: malformed PROXY protocol header")
ErrProxyProtoMalformed is returned when the PROXY header bytes cannot be parsed.
Functions ¶
func DNSResolver ¶
DNSResolver is the default NameResolver. It uses the Go runtime's default resolver (which respects /etc/resolv.conf and CGO settings) and is context-aware: deadlines and cancellation on ctx propagate to the underlying lookup. It returns the full list of A and AAAA addresses for name in resolver order; callers (typically the CONNECT handler) iterate through the returned slice to perform happy-eyeballs dialing. (C-10)
func DefaultForwarder ¶
DefaultForwarder is the default data forwarder. It copies data bidirectionally between src and dst using ProxyStream, which supports TCP half-close.
Prior versions accepted io.ReadWriter and type-asserted to net.Conn internally. The signature now matches reality. For pure io.ReadWriter use cases (in-process pipes, custom transports) call LiteForwarder directly.
func LiteForwarder ¶
func LiteForwarder(src, dst io.ReadWriter) error
LiteForwarder is a simple bidirectional copy without half-close, for io.ReadWriter pairs that do not provide net.Conn (e.g., io.Pipe halves). It returns the first non-nil error from either direction.
func MeteredForwarder ¶
MeteredForwarder returns a Forwarder that wraps DefaultForwarder (ProxyStream-based, with TCP half-close) and reports bytes proxied in each direction to m.AddBytesProxied.
Conventions:
- "outbound" counts bytes flowing from the client → target (the SOCKS5 client → the proxied destination).
- "inbound" counts bytes flowing from the target → client (the proxied destination → the SOCKS5 client).
When m is nil, NoOpMetrics is used so the wrapper degrades to the default behaviour at negligible cost. The returned function has the same signature and semantics as DefaultForwarder; in particular it honours the CloseWriter contract documented on ServerConfig.Forwarder (C-11) by forwarding CloseWrite through the shim.
Use it as ServerConfig.Forwarder = MeteredForwarder(myMetrics) to opt into per-byte accounting without re-implementing the half-close logic.
func NewStdLogger ¶
NewStdLogger returns a pointer to a zerolog.Logger writing to stderr with a consistent time format. This is the canonical logger factory for demos and external consumers.
func PermitCommand ¶
func PermitCommand(connect, bind, assoc bool) func(ctx context.Context, req *Request) (context.Context, bool)
PermitCommand returns a rule function that allows or denies specific SOCKS5 commands based on the given boolean flags.
func PermitDest ¶
PermitDest returns a ruleset that allows the request only when the resolved destination IP (req.realDestAddr.IP) belongs to one of the given CIDR ranges. Bare IPs are treated as /32 or /128. CIDRs that fail to parse cause a panic at construction time so misconfiguration is caught at startup. Requests whose realDestAddr has no IP (e.g. an FQDN destination whose NameResolver returned no addresses) are denied. (C-13)
func PermitNone ¶
PermitNone denies all commands.
func PermitPorts ¶
PermitPorts returns a ruleset that allows the request only when the destination port (req.DestAddr.Port) appears in the given list. An empty list denies all requests. (C-13)
func PermitSource ¶
PermitSource returns a ruleset that allows the request only when the client source IP (req.RemoteAddr) belongs to one of the given CIDR ranges. Non-IP remote addresses (e.g. in-process pipes) are denied. CIDR parse errors panic at construction. (C-13)
func ProxyStream ¶
ProxyStream bidirectionally copies data between a and b using ring buffers to decouple reads and writes. It supports TCP half-close: when one direction reaches EOF, it sends a FIN to signal the peer while keeping the other direction open.
B1 fixes applied:
- Cross-cancellation: when one direction errors, both ring buffers are closed immediately to prevent deadlocks.
- Read wraparound: explicit min(avail, end-of-slice) bounds.
- Pool: 32 KiB read buffers, 64 KiB ring buffers (Perf-04 also pools the ring buffer slabs).
- Error propagation: write errors close both directions.
func ReadMethods ¶
ReadMethods reads the list of authentication methods from the client's initial handshake. It reads 1 byte of count n, then n bytes of method IDs.
Optimized: uses a stack-allocated [8]byte array for the common case (most clients send 1-3 methods), falling back to heap allocation only for counts > 8.
Clean-03: the [8]byte stack path is justified by the BenchmarkReadMethods_Small / _Heap pair in bench_test.go — at NMETHODS<=8 (covering 99%+ of real clients) the stack path avoids a small-slice heap allocation per connection handshake. Profile before changing this path.
Types ¶
type AddrSpec ¶
type AddrSpec struct {
FQDN string // fully-qualified domain name (used when ATYP is DOMAINNAME)
IP net.IP // IP address (used when ATYP is IPv4 or IPv6)
Port int // port number
}
AddrSpec represents a SOCKS address as described in RFC 1928, section 5. It can hold an IPv4, IPv6, or domain name address along with a port.
func ParseAddrSpec ¶
ParseAddrSpec parses a "host:port" string into an *AddrSpec. The host may be an IPv4 address, an IPv6 address (in brackets), or a domain name. This is the inverse of AddrSpec.Address().
type AuthContext ¶
type AuthContext struct {
// Method is the authentication method that was used.
Method uint8
// Identity is the verified principal — for RFC 1929 username/password
// this is the authenticated username. It MUST NOT contain credentials.
// Empty when no identity is associated with the method (e.g. NoAuth).
// (Sec-06)
Identity string
// Payload carries method-specific data. Implementations MUST NEVER
// place passwords or other secrets here; loggers may write this map
// at debug level. Prefer Identity for the verified principal name.
// For backward compatibility, UserPassAuthenticator continues to set
// Payload["username"] alongside Identity. (Sec-06)
Payload map[string]string
}
AuthContext holds the result of the authentication handshake.
type Authenticator ¶
type Authenticator interface {
// Authenticate performs the method-specific sub-negotiation. It is
// invoked *after* Server.authenticate has written the
// method-selection reply (R-03).
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
// GetCode returns the method identifier byte for this authenticator.
GetCode() uint8
}
Authenticator handles SOCKS5 per-method authentication sub-negotiation (e.g., RFC 1929 username/password). Method negotiation (RFC 1928 §3) is performed by Server.authenticate, which writes the {VER, METHOD} method-selection reply itself; Authenticator implementations must not write that reply. (R-03)
On error, the caller closes the underlying connection. Implementations should not attempt to drain or recover the remaining sub-negotiation bytes; the connection-close is the synchronisation point. (R-05)
type BoundConn ¶
BoundConn is the connection returned by Client.Bind. It wraps the underlying TCP connection to the proxy with the addresses from both BIND reply phases.
func (*BoundConn) ListenAddr ¶
ListenAddr returns the proxy's listen address (phase-1 reply).
type BytesPool ¶
type BytesPool interface {
// Get returns a byte slice of at least n bytes.
Get(n int) []byte
// Put returns a byte slice to the pool.
Put(buf []byte)
}
BytesPool is a pool of byte slices used for UDP relay and other buffered operations. Implementations should reuse byte slices to reduce allocations.
type Client ¶
type Client struct {
*ClientConfig
}
Client dials network addresses through a SOCKS5 proxy. It embeds *ClientConfig anonymously so every config field is accessible as a Client field via Go's field promotion (e.g. c.ProxyAddr, c.Username).
Prior versions duplicated every ClientConfig field on Client and copied them in NewClient — the duplication has been removed and Client now holds a *ClientConfig directly.
func NewClient ¶
func NewClient(cfg *ClientConfig) *Client
NewClient returns a Client configured with the given ClientConfig. If cfg is nil, a default configuration is used (no auth, no timeout, default dialer).
func (*Client) Associate ¶
Associate performs a SOCKS5 UDP ASSOCIATE operation through the proxy.
The target address indicates the address and port the client expects to use to send UDP datagrams. Use "0.0.0.0:0" if unknown.
The returned *UDPRelay provides RelayAddr() — the proxy's UDP relay address where SOCKS5-encapsulated datagrams should be sent. The caller is responsible for creating a local net.PacketConn and exchanging datagrams with the relay. Closing the UDPRelay (or its control connection) terminates the association.
func (*Client) Bind ¶
Bind performs a SOCKS5 BIND operation through the proxy.
RFC 1928 §6 specifies a two-phase reply:
- Phase 1: the proxy creates a listening socket and replies with the listen address (BND.ADDR/BND.PORT).
- Phase 2: when an incoming connection arrives, the proxy sends a second reply with the peer's address.
The target address should indicate the expected peer for the incoming connection. The returned *BoundConn provides ListenAddr() (phase 1) and PeerAddr() (phase 2), and wraps the forwarded connection.
func (*Client) DialContext ¶
DialContext connects to targetAddr through the SOCKS5 proxy, using ctx for the initial dial and handshake. The returned net.Conn is a *SocksConn which provides access to the bound address via BoundAddr().
type ClientConfig ¶
type ClientConfig struct {
// ProxyAddr is the "host:port" of the SOCKS5 proxy.
ProxyAddr string
// Username and Password for RFC 1929 authentication. When Username
// is non-empty the client offers AuthMethodUserPass.
Username string
Password string
// Auth is an optional custom authenticator. For username/password
// auth prefer the Username/Password fields instead. Auth is used
// as a fallback for custom authentication methods only.
Auth Authenticator
// Dialer is the dialer used to connect to the proxy. If nil and
// DialFunc is also nil, a default net.Dialer is used.
Dialer *net.Dialer
// DialFunc is a custom dial function for connecting to the proxy.
// When set it takes precedence over Dialer. This allows custom
// transport layers (TLS, Unix sockets, SSH tunnels, etc.).
DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
// HandshakeTimeout is the maximum time for the SOCKS5 handshake
// (method negotiation + auth + request/reply). Zero means no
// separate handshake timeout; Dialer.Timeout or context deadlines
// still apply to the TCP connect and handshake respectively.
HandshakeTimeout time.Duration
// ResolveOnProxy enables SOCKS5h hostname-forwarding mode.
// When true, the client always sends the target hostname as an FQDN
// in the SOCKS5 request, leaving DNS resolution to the proxy. When
// false (default), the client resolves IP literals locally but still
// sends hostnames as FQDNs.
ResolveOnProxy bool
}
ClientConfig holds the configuration for a SOCKS5 client.
type CloseReader ¶
type CloseReader interface {
CloseRead() error
}
CloseReader is implemented by connections that support half-close (shutting down the read side).
type CloseWriter ¶
type CloseWriter interface {
CloseWrite() error
}
CloseWriter is implemented by connections that support half-close (TCP FIN on the write side).
type CredentialStore ¶
type CredentialStore interface {
// Valid returns true if the given username/password pair is valid.
Valid(user, password string) bool
}
CredentialStore is used to authenticate SOCKS5 username/password pairs as defined in RFC 1929.
type Datagram ¶
type Datagram struct {
// Frag is the SOCKS5 UDP fragment field (RFC 1928 §7). Bit 0x80
// marks the end-of-sequence fragment; the low 7 bits encode the
// position (1..127). FRAG=0x00 means a standalone (non-fragmented)
// datagram. (Clean-06)
Frag uint8
DstAddr *AddrSpec
Data []byte
}
Datagram represents a SOCKS5 UDP relay datagram (RFC 1928 §7).
+----+------+------+----------+----------+----------+ |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | +----+------+------+----------+----------+----------+ | 2 | 1 | 1 | Variable | 2 | Variable | +----+------+------+----------+----------+----------+
func NewDatagram ¶
NewDatagram creates a new Datagram with the given parameters.
func ParseDatagram ¶
ParseDatagram parses raw UDP payload into a Datagram with strict RFC 1928 §7 RSV validation (non-zero RSV bytes are rejected).
The returned Data is a copy of the payload's data portion and is safe to retain after the input buffer is reused.
For lenient parsing (e.g., to accept legacy clients with non-zero RSV bytes), see ServerConfig.LenientUDPRSV. (R-09)
func (*Datagram) AppendTo ¶
AppendTo appends the wire-format datagram (RSV+FRAG+addr+data) to buf and returns the extended slice. It allocates only when buf has insufficient capacity, which makes it suitable for hot-path reuse with a pre-grown scratch slice. (Perf-10)
type Handler ¶
Handler is a function that handles a SOCKS5 request. It receives the context, an io.Writer (the client connection), and the request.
type Listener ¶
type Listener struct {
// contains filtered or unexported fields
}
Listener implements net.Listener using in-memory pipe connections. It allows an HTTP server (or any net.Listener consumer) to accept connections without a real TCP socket. When paired with a SOCKS5 proxy via ServerConfig.Listeners, CONNECT requests to the listener's address are routed through the virtual listener instead of making an outbound TCP dial.
Example:
vl := socks5.NewListener("127.0.0.1:0")
go http.Serve(vl, mux)
server, _ := socks5.NewServer(&socks5.ServerConfig{
Listeners: map[string]*socks5.Listener{
vl.Addr().String(): vl,
},
})
func NewListener ¶
NewListener creates a Listener on the given address. The addr string must be "host:port". If port is "0", a random high port (>= 49152) is assigned so the address is unique per listener.
func (*Listener) Accept ¶
Accept waits for and returns the next connection to the listener. The returned connection is one end of a net.Pipe; the other end is provided to the caller that dialed into this listener.
type MethodReply ¶
MethodReply represents the server's method selection response.
+----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+
func NewMethodReply ¶
func NewMethodReply(ver, method uint8) *MethodReply
NewMethodReply creates a MethodReply.
func ParseMethodReply ¶
func ParseMethodReply(r io.Reader) (*MethodReply, error)
ParseMethodReply reads a method reply from r.
func (*MethodReply) Bytes ¶
func (m *MethodReply) Bytes() []byte
Bytes serializes the MethodReply to wire format.
type MethodRequest ¶
MethodRequest represents the client's initial method selection message.
+----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+
func NewMethodRequest ¶
func NewMethodRequest(ver uint8, methods []byte) *MethodRequest
NewMethodRequest creates a MethodRequest.
func ParseMethodRequest ¶
func ParseMethodRequest(r io.Reader) (*MethodRequest, error)
ParseMethodRequest reads a method request from r.
func (*MethodRequest) Bytes ¶
func (m *MethodRequest) Bytes() []byte
Bytes serializes the MethodRequest to wire format.
type Metrics ¶
type Metrics interface {
// IncConnectionsAccepted is called for every accepted TCP connection,
// before any per-IP limit or semaphore check.
IncConnectionsAccepted(ctx context.Context)
// IncConnectionsRejected is called when a connection is rejected by a
// per-IP limit, an auth failure, or a ruleset denial. The reason
// string is a stable short identifier such as "per_ip_limit",
// "auth_failed", or "rule_denied".
IncConnectionsRejected(ctx context.Context, reason string)
// IncCommand is called once per accepted CONNECT/BIND/ASSOCIATE,
// after the request has passed rule checks. The command argument
// is one of CommandConnect, CommandBind, CommandAssociate.
IncCommand(ctx context.Context, command uint8)
// IncAuthFailure is called every time an Authenticator returns an
// error. The method byte identifies the authenticator (e.g.,
// AuthMethodUserPass).
IncAuthFailure(ctx context.Context, method uint8)
// AddBytesProxied may be called by custom Forwarders to report the
// number of bytes transferred in a given direction ("inbound" or
// "outbound"). The built-in DefaultForwarder does not currently
// emit this event; it is exposed for users who wrap their own
// Forwarder and want per-byte accounting.
AddBytesProxied(ctx context.Context, direction string, n int64)
}
Metrics is a counter/gauge interface that the SOCKS5 server uses to emit operational metrics. It is intentionally narrow: each method is a hot-path callback and should not block. Implementations must be safe for concurrent use. (C-07)
Pass a Metrics into ServerConfig.Metrics to wire it into the server. When nil, NoOpMetrics is used by default.
type Middleware ¶
Middleware wraps a Handler to add cross-cutting behavior such as logging, rate limiting, or metrics collection.
type MiddlewareChain ¶
type MiddlewareChain []Middleware
MiddlewareChain is an ordered list of middleware that can be applied to a handler. Middleware is executed in order: the first middleware in the chain is the outermost wrapper.
func (MiddlewareChain) Then ¶
func (c MiddlewareChain) Then(h Handler) Handler
Then applies the middleware chain to the given handler, returning a new handler with all middleware applied.
type NoAuthAuthenticator ¶
type NoAuthAuthenticator struct{}
NoAuthAuthenticator implements the NO AUTHENTICATION REQUIRED method (method 0x00).
func (NoAuthAuthenticator) Authenticate ¶
func (a NoAuthAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
Authenticate is a no-op for NoAuthAuthenticator: the method-selection reply is written by Server.authenticate (R-03), and NoAuth has no sub-negotiation. Returns an empty AuthContext.
func (NoAuthAuthenticator) GetCode ¶
func (a NoAuthAuthenticator) GetCode() uint8
GetCode returns 0x00.
type NoOpMetrics ¶
type NoOpMetrics struct{}
NoOpMetrics is a Metrics implementation that drops every event. It is the default value of ServerConfig.Metrics when none is configured.
func (NoOpMetrics) AddBytesProxied ¶
func (NoOpMetrics) AddBytesProxied(_ context.Context, _ string, _ int64)
AddBytesProxied implements Metrics.
func (NoOpMetrics) IncAuthFailure ¶
func (NoOpMetrics) IncAuthFailure(_ context.Context, _ uint8)
IncAuthFailure implements Metrics.
func (NoOpMetrics) IncCommand ¶
func (NoOpMetrics) IncCommand(_ context.Context, _ uint8)
IncCommand implements Metrics.
func (NoOpMetrics) IncConnectionsAccepted ¶
func (NoOpMetrics) IncConnectionsAccepted(_ context.Context)
IncConnectionsAccepted implements Metrics.
func (NoOpMetrics) IncConnectionsRejected ¶
func (NoOpMetrics) IncConnectionsRejected(_ context.Context, _ string)
IncConnectionsRejected implements Metrics.
type NoOpObserver ¶
type NoOpObserver struct{}
NoOpObserver is an Observer that does nothing. Use it as a base when you only need to implement a subset of the hooks.
func (NoOpObserver) OnAssociate ¶
func (NoOpObserver) OnAssociate(_ context.Context, _ *Request, _ net.PacketConn)
OnAssociate implements Observer.
type Observer ¶
type Observer interface {
// OnConnect is called after a CONNECT request is accepted and the
// target connection is established.
OnConnect(ctx context.Context, req *Request, target net.Conn)
// OnBind is called after a BIND listener is successfully created.
OnBind(ctx context.Context, req *Request, listener net.Listener)
// OnAssociate is called after a UDP ASSOCIATE is set up.
OnAssociate(ctx context.Context, req *Request, conn net.PacketConn)
// OnError is called when an error occurs during request handling.
OnError(ctx context.Context, req *Request, err error)
// OnShutdown is called when the server has finished shutting down.
OnShutdown()
}
Observer provides lifecycle hooks into the SOCKS5 server. All methods are called synchronously from the server's goroutines, so implementations should not block.
Each method receives the context which may carry tracing or deadline information. Implementations can use these hooks for metrics, logging, or distributed tracing (e.g., OpenTelemetry span creation).
type PacketConn ¶
type PacketConn struct {
// contains filtered or unexported fields
}
PacketConn implements net.PacketConn using in-memory buffered channels. It can be used in conjunction with Listener to create a fully in-process network stack for the SOCKS5 proxy.
func NewPacketConn ¶
func NewPacketConn(addr string) (*PacketConn, error)
NewPacketConn creates a PacketConn bound to the given address. The addr string must be "host:port". If port is "0", a random high port is assigned.
func (*PacketConn) LocalAddr ¶
func (vpc *PacketConn) LocalAddr() net.Addr
LocalAddr returns the local network address.
func (*PacketConn) ReadFrom ¶
ReadFrom reads a packet from the connection. It blocks until a packet is written via WriteTo.
func (*PacketConn) SetDeadline ¶
func (vpc *PacketConn) SetDeadline(t time.Time) error
SetDeadline is a no-op for virtual packet connections.
func (*PacketConn) SetReadDeadline ¶
func (vpc *PacketConn) SetReadDeadline(t time.Time) error
SetReadDeadline is a no-op for virtual packet connections.
func (*PacketConn) SetWriteDeadline ¶
func (vpc *PacketConn) SetWriteDeadline(t time.Time) error
SetWriteDeadline is a no-op for virtual packet connections.
type ProtoReply ¶
ProtoReply represents a SOCKS5 protocol-level reply message.
+----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+
func NewProtoReply ¶
func NewProtoReply(reply uint8, bnd *AddrSpec) *ProtoReply
NewProtoReply creates a ProtoReply.
func ParseProtoReply ¶
func ParseProtoReply(r io.Reader) (*ProtoReply, error)
ParseProtoReply reads a SOCKS5 reply from r.
func (*ProtoReply) Bytes ¶
func (p *ProtoReply) Bytes() []byte
Bytes serializes the ProtoReply to wire format. (Clean-09: single-allocation builder using appendAddrSpec; no bytes.Buffer.)
type ProtoRequest ¶
ProtoRequest represents a SOCKS5 protocol-level request message. Named ProtoRequest to avoid collision with the existing Request struct (which also carries auth context and connection state).
+----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+
func NewProtoRequest ¶
func NewProtoRequest(cmd uint8, dst *AddrSpec) *ProtoRequest
NewProtoRequest creates a ProtoRequest.
func ParseProtoRequest ¶
func ParseProtoRequest(r io.Reader) (*ProtoRequest, error)
ParseProtoRequest reads a SOCKS5 request from r.
func (*ProtoRequest) Bytes ¶
func (p *ProtoRequest) Bytes() []byte
Bytes serializes the ProtoRequest to wire format. (Clean-09: single-allocation builder using appendAddrSpec, matching the addr.go pattern; no bytes.Buffer.)
Returns nil when DstAddr.FQDN exceeds 255 bytes (RFC 1928 §4). Callers should use Validate() before calling Bytes() to catch this condition proactively.
func (*ProtoRequest) Validate ¶
func (p *ProtoRequest) Validate() error
Validate checks that the ProtoRequest is well-formed per RFC 1928. Currently checks that the FQDN (if set) is ≤ 255 bytes.
type ReplyError ¶
ReplyError represents a SOCKS5 reply error with the reply code.
func (*ReplyError) Error ¶
func (e *ReplyError) Error() string
type Request ¶
type Request struct {
Version uint8
Command uint8
Reserved uint8
AuthContext *AuthContext
RemoteAddr net.Addr
DestAddr *AddrSpec
// contains filtered or unexported fields
}
Request represents a parsed SOCKS5 request from the client.
func NewRequest ¶
NewRequest reads and parses a SOCKS5 request from bufConn. It reads the VER + CMD + RSV header and the destination address. Command validation is NOT performed here — it is deferred to handleRequest so that proper SOCKS5 error replies can be sent.
type RoutinePool ¶
type RoutinePool interface {
// Submit submits a function for execution. It may block if the
// pool is at capacity. Returns an error if the task cannot be
// submitted (e.g., pool is shut down).
Submit(f func()) error
}
RoutinePool is a goroutine pool interface. Implementations can limit the number of concurrent goroutines used for connection handling.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server implements a SOCKS5 server.
func NewServer ¶
func NewServer(cfg *ServerConfig) (*Server, error)
NewServer creates a new SOCKS5 server with the given configuration. If cfg is nil, defaults are used. applyDefaults fills in any zero-valued fields.
func (*Server) ListenAndServe ¶
ListenAndServe creates a listener and starts serving SOCKS5 requests. It blocks until the server is shut down or an error occurs.
func (*Server) Serve ¶
Serve starts serving SOCKS5 requests on the given listener. It blocks until the listener is closed or the server is shut down.
func (*Server) ServeConn ¶
ServeConn performs a full SOCKS5 handshake on an established connection.
RFC 1928 compliance notes:
- [E4] Unknown commands reach handleRequest which sends ReplyCommandNotSupported.
- [E5] Unknown ATYP in the request causes ReplyAddrTypeNotSupported to be sent.
- [E7] Non-zero RSV byte is handled per StrictRSV config: rejected or logged.
func (*Server) ServeConnNoAuth ¶
ServeConnNoAuth skips the version check and authentication, reading the SOCKS request directly from the connection. This is useful when the connection has already been authenticated (e.g., by TLS client certs).
RFC 1928 compliance notes: same as ServeConn (E5, E7).
func (*Server) Shutdown ¶
Shutdown gracefully shuts down the server.
Steps:
- Cancel the server context so accept loops and worker goroutines observe the shutdown signal (cancel() is the single source of truth for "shutting down").
- Close the listener exactly once (sync.Once) so Accept() returns.
- Wait for active handlers to drain or for ctx to expire and then force-close any remaining tracked connections.
- Invoke Observer.OnShutdown() exactly once after the wait completes, regardless of how Shutdown returned (R-02).
type ServerConfig ¶
type ServerConfig struct {
// AuthMethods is the list of supported authentication methods, tried
// in order. If nil, NoAuthAuthenticator is used.
AuthMethods []Authenticator
// NameResolver resolves domain names to IP addresses. If nil,
// DNSResolver is used. The returned slice should be in resolver
// order; the CONNECT handler tries each address in turn
// (happy-eyeballs) until one succeeds. BIND and ASSOCIATE use only
// the first address. (C-10)
NameResolver func(ctx context.Context, name string) (context.Context, []net.IP, error)
// RuleSet determines whether to allow or deny requests. If nil,
// PermitAll is used.
RuleSet func(ctx context.Context, req *Request) (context.Context, bool)
// AddressRewriter optionally rewrites the destination address before
// connecting. If nil, no rewriting is performed.
AddressRewriter func(ctx context.Context, request *Request) (context.Context, *AddrSpec)
// Logger is used to log errors and informational messages. If nil,
// a default zerolog.Logger writing to stderr is used.
Logger *zerolog.Logger
// Dial is the function used to establish outbound TCP connections for
// CONNECT requests. If nil, net.Dialer is used.
Dial func(ctx context.Context, network, addr string) (net.Conn, error)
// BindIP is the IP address to bind to for BIND and ASSOCIATE replies.
// If nil, 0.0.0.0 is used.
BindIP net.IP
// BindPort is the port to bind to for BIND and ASSOCIATE replies.
// If zero, a random port is chosen.
BindPort int
// ProxyListen creates a TCP listener with custom control (e.g., for
// transparent proxying or reuseport). If nil, net.Listen is used.
ProxyListen func(ctx context.Context, network, addr string) (net.Listener, error)
// ProxyListenPacket creates a UDP packet connection. If nil,
// net.ListenPacket is used.
ProxyListenPacket func(ctx context.Context, network, addr string) (net.PacketConn, error)
// ProxyOutgoingListenPacket creates a UDP packet connection for
// outgoing relay traffic. If nil, ProxyListenPacket (or net.ListenPacket)
// is used.
ProxyOutgoingListenPacket func(ctx context.Context, network, addr string) (net.PacketConn, error)
// PacketForwardAddress determines the source address for forwarded
// UDP packets. If nil, the default implementation returns the local
// address of the packet connection.
PacketForwardAddress func(ctx context.Context, destinationAddr string, packet net.PacketConn, conn net.Conn) (net.IP, int, error)
// ProxyListenBind creates a TCP listener specifically for BIND
// requests. If nil, ProxyListen (or net.Listen) is used.
ProxyListenBind func(ctx context.Context, network, addr string) (net.Listener, error)
// ListenBindReuseTimeout is the duration for which a BIND listener
// port can be reused after the previous binding is released.
ListenBindReuseTimeout time.Duration
// ListenBindAcceptTimeout is the maximum time to wait for an incoming
// connection on a BIND listener.
ListenBindAcceptTimeout time.Duration
// Forwarder copies data between client and target connections. If
// nil, DefaultForwarder is used.
//
// The signature was changed from func(src, dst io.ReadWriter)
// error to func(src, dst net.Conn) error. The previous signature
// type-asserted to net.Conn internally; making net.Conn explicit
// removes a hidden interface requirement. For pure io.ReadWriter
// use cases (e.g., in-process pipes) call LiteForwarder directly.
//
// C-11: custom Forwarders MUST perform a TCP half-close (CloseWrite)
// on EOF in each direction so that BIND-style two-way streams finish
// cleanly; otherwise long-lived peers may stall waiting for FIN. The
// built-in DefaultForwarder (backed by ProxyStream) already honours
// this contract via the CloseWriter interface.
//
// MeteredForwarder(metrics) is the canonical wrapper for
// reporting per-byte traffic to a Metrics implementation without
// re-implementing the half-close logic.
Forwarder func(src, dst net.Conn) error
// Observer receives lifecycle events. If nil, NoOpObserver is used.
Observer Observer
// Metrics receives operational counter events such as accepted
// connections, rejected connections, command counts and auth
// failures. If nil, NoOpMetrics is used. (C-07)
Metrics Metrics
// IdleTimeout is the maximum time a connection can remain idle before
// being closed. Zero means no idle timeout.
IdleTimeout time.Duration
// BytesPool is used to allocate byte slices. If nil, a default pool
// is used.
BytesPool BytesPool
// Context is the base context for the server. If nil, context.Background
// is used.
Context context.Context
// FragmentTimeout is the maximum time to wait for a complete sequence
// of UDP fragments before discarding. Default is 5 seconds.
// (RFC 1928 §7 requires a reassembly timer.)
FragmentTimeout time.Duration
// FragmentPoolSize is the maximum byte size for which the fragment
// reassembler uses a sync.Pool to reuse allocations. Fragments larger
// than this are heap-allocated. Default 1500 (typical MTU). (P3-2)
FragmentPoolSize int
// MaxDatagramSize limits the maximum acceptable UDP datagram payload
// size. Datagrams exceeding this limit are silently dropped.
// Zero means no limit (up to 65535 bytes).
MaxDatagramSize int
// RateLimit limits per-client UDP datagram throughput (tokens per
// second). Zero means no rate limiting.
RateLimit float64
// StrictRSV controls whether a non-zero RSV byte in the SOCKS5
// request header causes the connection to be rejected. When false
// (default), a non-zero RSV is logged but the request proceeds.
// When true, the server replies with ReplyGeneralFailure.
StrictRSV bool
// LenientUDPRSV, when true, allows non-zero RSV bytes in incoming
// UDP relay datagrams (logged but accepted). Default false (strict
// rejection per RFC 1928 §7). This is the UDP counterpart of the
// TCP-side StrictRSV escape hatch. (R-09)
//
// Note: the field is inverted vs. StrictRSV so that the zero value
// preserves the current strict / RFC-compliant behaviour.
LenientUDPRSV bool
// StrictAssociateBinding, when true, pins both IP *and* port from
// the first datagram on late-binding UDP ASSOCIATE sessions
// (DST.ADDR=0.0.0.0:0). When false (default), only the IP is
// pinned, matching prior behaviour. (R-04)
StrictAssociateBinding bool
// StrictBindOrigin, when true, refuses BIND requests that leave
// the expected peer address unspecified (DST.ADDR=0.0.0.0:0).
// RFC 1928 §6 lets the client leave the peer unspecified and
// resolve it on the first accepted connection, but that lets
// any peer take over the bind slot. With StrictBindOrigin=true,
// the server replies with ReplyConnectionNotAllowed instead of
// opening a wildcard listener, forcing the client to pre-commit
// to a specific peer. (Sec-05)
StrictBindOrigin bool
// RoutinePool is an optional goroutine pool for limiting concurrency.
// If nil, a new goroutine is spawned per connection.
RoutinePool RoutinePool
// ConnectHandle, BindHandle, AssociateHandle are optional custom
// handlers that replace the default implementation for each command.
ConnectHandle Handler
BindHandle Handler
AssociateHandle Handler
// Listeners maps listener addresses to in-process virtual
// listeners. When a CONNECT request targets a virtual listener,
// the server routes the connection through the listener instead
// of making an outbound TCP dial. The map key must match the
// address returned by the listener's Addr().String().
Listeners map[string]*Listener
// ConnectMiddleware, BindMiddleware, AssociateMiddleware are optional
// middleware chains applied to each command handler.
ConnectMiddleware MiddlewareChain
BindMiddleware MiddlewareChain
AssociateMiddleware MiddlewareChain
// MaxConnections limits concurrent connections. Zero means no limit.
// When the limit is reached, new connections block until a slot is
// available. (D2)
MaxConnections int
// MaxConnectionsPerIP limits the number of concurrent connections from
// a single source IP. Zero means no per-IP limit (only the global
// MaxConnections, if set, applies). When the limit is reached, new
// connections from that IP are accepted at the TCP layer but
// immediately closed by the server. (C-04)
MaxConnectionsPerIP int
// HandshakeTimeout is the maximum time for the SOCKS5 handshake
// (version check + auth + request). If zero, no handshake timeout
// is applied (IdleTimeout still covers the whole connection). (D6)
HandshakeTimeout time.Duration
// AssociateSetupTimeout is the maximum time the server waits for the
// first datagram from the expected UDP client after a successful
// ASSOCIATE reply. If no datagram arrives in time, the association is
// torn down. Zero disables the timeout. (Sec-02 — slow-loris defence
// on UDP ASSOCIATE setup.)
AssociateSetupTimeout time.Duration
// ReadBufferSize sets the kernel read buffer (SO_RCVBUF) on accepted
// connections. Zero means use OS default. (D7)
ReadBufferSize int
// WriteBufferSize sets the kernel write buffer (SO_SNDBUF) on accepted
// connections. Zero means use OS default. (D7)
WriteBufferSize int
// KeepAlivePeriod controls TCP keep-alive on accepted connections.
// When > 0, SetKeepAlive(true) is called and the keep-alive period
// is set to this value on every accepted *net.TCPConn. Zero leaves
// the keep-alive setting at the OS default (typically off on most
// kernels for accepted sockets). Useful for long-lived CONNECT
// tunnels behind NAT where dead-peer detection would otherwise
// take minutes or hours. (C-09)
KeepAlivePeriod time.Duration
// TLSConfig optionally wraps the accept-side listener in
// tls.NewListener before the SOCKS5 version byte is read. The TLS
// handshake completes first, then the SOCKS5 protocol runs on the
// inner cleartext stream. This is the symmetric counterpart to
// ClientConfig.DialFunc: clients that need to speak SOCKS5-over-TLS
// already wrap their dialer; the server side now has an equivalent
// one-liner.
//
// When non-nil, both Server.Serve and Server.ListenAndServe wrap
// the listener via tls.NewListener(ln, TLSConfig). The TLS
// handshake is lazy (triggered by the first Read/Write on the
// *tls.Conn) and thus respects the connection deadline set by
// HandshakeTimeout before the SOCKS5 negotiation begins.
// A separate TLSHandshakeTimeout is therefore unnecessary in the
// common case; if the OS TCP timeout is insufficient, set
// HandshakeTimeout to bound both the TLS and SOCKS5 phases. (Sec-15)
TLSConfig *tls.Config
// AcceptProxyProtocol, when true, makes the server read a HAProxy
// PROXY protocol v1 (text) or v2 (binary) header from each accepted
// connection BEFORE the SOCKS5 version byte. The source address
// from the header replaces the underlying TCP RemoteAddr() for the
// rest of the connection's lifetime, which means MaxConnectionsPerIP
// (C-04) and PermitSource (C-13) see the real client behind an L4
// load balancer. When the header is malformed, the connection is
// closed before any SOCKS5 negotiation occurs.
//
// Use this only when every accepted connection comes from a trusted
// proxy that always emits the header (e.g. an HAProxy / AWS NLB
// frontend). Enabling it on a public listener allows attackers to
// spoof their source IP.
AcceptProxyProtocol bool
}
ServerConfig holds the configuration for a SOCKS5 server. After constructing one, it can be passed directly to NewServer. Zero-value fields are filled with sensible defaults by applyDefaults.
type StaticCredentials ¶
StaticCredentials is a map-based credential store backed by an in-memory username -> password map. Password comparison uses crypto/subtle.ConstantTimeCompare to avoid revealing the password length or the location of the first mismatching byte through timing side-channels (Sec-04). When the username is unknown, a dummy compare against a fixed-length scratch buffer is still performed so that the presence or absence of a user cannot be inferred from response time.
func (StaticCredentials) Valid ¶
func (s StaticCredentials) Valid(user, password string) bool
Valid implements CredentialStore using a constant-time comparison.
type UDPRelay ¶
type UDPRelay struct {
// contains filtered or unexported fields
}
UDPRelay is returned by Client.Associate. It holds the proxy's TCP control connection (which must stay open) and the UDP relay address where datagrams should be sent.
type UserPassAuthenticator ¶
type UserPassAuthenticator struct {
Credentials CredentialStore
}
UserPassAuthenticator implements the USERNAME/PASSWORD authentication method (method 0x02) as defined in RFC 1929. It validates credentials against the provided CredentialStore.
func (UserPassAuthenticator) Authenticate ¶
func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
Authenticate performs the RFC 1929 sub-negotiation: the client sends a username/password, the server validates it, and writes a status byte.
The {VER, METHOD} method-selection reply is written by Server.authenticate before this method is called (R-03). On error the caller closes the connection (R-05).
func (UserPassAuthenticator) GetCode ¶
func (a UserPassAuthenticator) GetCode() uint8
GetCode returns 0x02.
type UserPassReply ¶
UserPassReply represents an RFC 1929 username/password reply.
+----+--------+ |VER | STATUS | +----+--------+ | 1 | 1 | +----+--------+
func NewUserPassReply ¶
func NewUserPassReply(ver, status uint8) *UserPassReply
NewUserPassReply creates a UserPassReply.
func ParseUserPassReply ¶
func ParseUserPassReply(r io.Reader) (*UserPassReply, error)
ParseUserPassReply reads an RFC 1929 auth reply from r.
func (*UserPassReply) Bytes ¶
func (u *UserPassReply) Bytes() []byte
Bytes serializes the UserPassReply to wire format.
type UserPassRequest ¶
UserPassRequest represents an RFC 1929 username/password request.
+----+------+----------+------+----------+ |VER | ULEN | UNAME | PLEN | PASSWD | +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 | +----+------+----------+------+----------+
func NewUserPassRequest ¶
func NewUserPassRequest(ver uint8, user, pass string) *UserPassRequest
NewUserPassRequest creates a UserPassRequest.
func ParseUserPassRequest ¶
func ParseUserPassRequest(r io.Reader) (*UserPassRequest, error)
ParseUserPassRequest reads an RFC 1929 auth request from r.
func (*UserPassRequest) Bytes ¶
func (u *UserPassRequest) Bytes() []byte
Bytes serializes the UserPassRequest to wire format.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
demo
|
|
|
acl
command
|
|
|
basic
command
|
|
|
chained
command
|
|
|
customdial
command
|
|
|
middleware
command
|
|
|
observer
command
|
|
|
packetconn
command
|
|
|
production
command
|
|
|
resolver
command
|
|
|
rewriter
command
|
|
|
shutdown
command
|
|
|
userpass
command
|
|
|
webserver
command
|