Documentation
¶
Overview ¶
Description ¶
Package socksy5 provides a SOCKS5 middle layer and utils for simple request handling. MidLayer implements the middle layer, which accepts client connections in the form of net.Conn (see MidLayer.ServeClient), then wraps client handshakes and requests as structs, letting external code to decide whether accept or reject, which kind of subnegotiation to use e.t.c..
This provides advantages when you need multi-homed BND or UDP ASSOCIATION processing, custom subnegotiation and encryption, attaching special connection to CONNECT requests.
Besides that, socksy5 also provides Connect, Binder and Associator as simple handlers for CONNECT, BND and UDP ASSOC requests. Listen is also provided as a simple listening util which passes net.Conn to MidLayer automatically. They are for ease of use if you want to set up a SOCKS5 server fast, thus they only have basic features. You can handle handshakes and requests yourself if they don't meet your requirement.
How to use ¶
First pass a net.Conn to a MidLayer instance, then MidLayer will begin communicating with the client. When client begins handshakes or sends requests, MidLayer will emit Handshake, ConnectRequest, BindRequest and AssocRequest via channels. Call methods of them to decide which kind of authentication to use, whether accept or reject and so on. Logs are emitted via channels too. See MidLayer.LogChan, MidLayer.HandshakeChan, MidLayer.RequestChan. User of this package should read Request, as it contains general info about different types of requests.
Note ¶
socksy5 provides limited implementations of authenticate methods, for quite a long time. MidLayer does relay TCP traffic, but it doesn't dial outbound or relay UDP traffic.
Index ¶
- Constants
- Variables
- func Connect(req *ConnectRequest) error
- func Listen(addr string, ml *MidLayer) error
- type ATYPNotSupportedError
- type AddrPort
- type AssocRequest
- type Associator
- type BindRequest
- type Binder
- type Capsulator
- type CmdNotSupportedError
- type ConnectRequest
- type Handshake
- type LogEntry
- type MidLayer
- type NoAuthSubneg
- type NoCap
- type OpError
- type RelayError
- type Request
- type RequestNotHandledError
- type RsvViolationError
- type Subnegotiator
- type UsrPwdSubneg
- type UsrPwdVerIncorrectError
- type VerIncorrectError
Constants ¶
const ( VerUsrPwd byte = 0x01 UsrPwdStatSuccess byte = 0x00 )
Constants used in Username/Password Authentication for SOCKS V5 (RFC 1929).
const ( SeverityDebug = "debug" SeverityInfo = "info" SeverityWarning = "warning" SeverityError = "error" )
Severity levels used by LogEntry.
const ( // Channel capacity of all channels returned by MidLayer's channel methods. ChanCap = 64 // Time to close connection if auth failed, request denied, e.t.c.. PeriodClose = time.Second * time.Duration(3) // Time to wait for external code to react to handshakes and requests. PeriodAutoDeny = time.Second * time.Duration(30) )
Constants of MidLayer policy.
const ( MethodNoAuth byte = 0x00 MethodGSSAPI byte = 0x01 MethodUsrPwd byte = 0x02 MethodCHAP byte = 0x03 MethodCRAM byte = 0x05 MethodSSL byte = 0x06 MethodNDS byte = 0x07 MethodMAF byte = 0x08 MethodJRB byte = 0x09 MethodNoAccepted byte = 0xFF )
Authentication METHOD codes.
const ( CmdCONNECT byte = 0x01 CmdBIND byte = 0x02 CmdASSOC byte = 0x03 )
CMD codes.
const ( ATYPV4 byte = 0x01 // IPv4 ATYPDOMAIN byte = 0x03 // Fully-qualified domain name ATYPV6 byte = 0x04 // IPv6 )
ATYP codes (address types)
const ( RepSucceeded byte = 0x00 RepGeneralFailure byte = 0x01 RepConnNotAllowedByRuleset byte = 0x02 RepNetworkUnreachable byte = 0x03 RepHostUnreachable byte = 0x04 RepConnRefused byte = 0x05 RepTtlExpired byte = 0x06 RepCmdNotSupported byte = 0x07 RepAddrTypeNotSupported byte = 0x08 )
REP reply codes.
const RSV byte = 0x00
Value of reserved bytes
const VerSOCKS5 byte = 0x05
SOCKS5 VER byte
Variables ¶
var ErrAcceptOrDenyFailed = errors.New("request already handled")
ErrAcceptOrDenyFailed is used by Connect, Binder and Associator. It indicates that the accept and deny methods of the request returned not ok.
var ErrAuthFailed = errors.New("auth failed")
var ErrDuplicatedRequest = errors.New("duplicated request with same parameters")
ErrDuplicatedRequest is returned by Associator.Handle and Binder.Handle indicating that another request with same parameters is being handled, e.g. the Binder is already listening the address stated by the BIND request.
var ErrMalformed = errors.New("malformed")
ErrMalformed represents protocol format violation. Usually a more specific error type is used: VerIncorrectError, RsvViolationError, CmdNotSupportedError, ATYPNotSupportedError and [UsrPwdVerIncorrectErr].
Functions ¶
func Connect ¶
func Connect(req *ConnectRequest) error
Connect handles the CONNECT request, accepting or denying it accordingly.
Currently if req is to be denied, only RepGeneralFailure will be replied.
Types ¶
type ATYPNotSupportedError ¶
type ATYPNotSupportedError byte
func (ATYPNotSupportedError) Error ¶
func (e ATYPNotSupportedError) Error() string
func (ATYPNotSupportedError) Is ¶
func (e ATYPNotSupportedError) Is(target error) bool
Is returns true if target is ErrMalformed.
func (ATYPNotSupportedError) Unwrap ¶
func (e ATYPNotSupportedError) Unwrap() error
Unwrap returns errors.ErrUnsupported.
type AddrPort ¶
type AddrPort struct { // ATYP byte value Type byte // If ATYP is ATYPDOMAIN, // the first byte that specifies the FQDN length is omitted, // length of Addr represents it implicitly. Addr []byte Port uint16 // One of "tcp" and "udp", [AddrPort.Network] relies on this field. Protocol string }
An AddrPort represents the address and the port sent in SOCKS5 requests and replies.
func ParseAddrPort ¶
ParseAddrPort parses s to AddrPort. If s is not a valid IP address, ParseAddrPort will try parse it as <host name>:<port>, WITHOUT syntax checking on the host name.
In the returned AddrPort, [AddrPort.Protocol] will be empty.
func (*AddrPort) Equal ¶
Equal tests whether a and x are the same address, returns false if either a or x is nil. Both a.Protocol and x.Protocol are ignored.
func (*AddrPort) MarshalBinary ¶
MarshalBinary returns raw bytes used in SOCKS5 traffic. (ATYP+ADDR+PORT)
func (*AddrPort) Network ¶
Network returns the network of a. If a is nil, "<nil>" is returned. If a.Type is one of ATYPV4 or ATYPV6, Network will append "4" or "6" to a.Protocol accordingly. If a.Type is ATYPDOMAIN, Network will just return a.Protocol. Otherwise, Network returns the hex of a.Type.
type AssocRequest ¶
type AssocRequest struct { Request // contains filtered or unexported fields }
func (*AssocRequest) Accept ¶
func (r *AssocRequest) Accept(addr string, notify func(reason error)) (terminate func() error, ok bool)
Accept accepts the request.
terminate can be used to terminate the association by closing the control connection. Be aware it is nil if ok is false.
notify is called when the association terminates, e.g. TCP disconnection, IO error, call on terminate. If the client closed the control connection, reason will be io.EOF. If terminate is called, reason will be nil. Otherwise, reason will be the read error on the control connection. notify will only be called once, if it's not nil.
type Associator ¶
type Associator struct { // Hostname of the server, not to be confused with listening address. // This is the address that will be sent in the first BND reply. // RFC 1928 states that addresses in replies to UDP ASSOCIATE requests // shall be IP addresses, but a host name is considered valid here. // Do not modify this field when Associator is running. Hostname string // contains filtered or unexported fields }
Associator relays UDP packets for UDP ASSOCIATE requests.
All methods of Associator can be called simultaneously.
func (*Associator) Handle ¶
func (a *Associator) Handle(req *AssocRequest, addr string) error
Handle handles the UDP ASSOCIATE request req.
addr is the address that a will listen for UDP packets from the client and send UDP packets to the client. It can be empty, in that case a will use all zero addresses with a system allocated port. If the host part in addr is a host name, Handle will look it up and listen on all of the resulting IP addresses. If the port in addr is 0, a system allocated port will be chosen. Note that if a host name is used in addr, Handle will duplicate host-to-client UDP packets and send them out using all of the IP addresses associated with the FQDN.
type BindRequest ¶
type BindRequest struct { Request // contains filtered or unexported fields }
func (*BindRequest) Accept ¶
func (r *BindRequest) Accept(addr string) (ok bool)
Accept accepts the request, and tells the client which address the SOCKS server will listen on. This is the first reply from the server.
func (*BindRequest) Bind ¶
func (r *BindRequest) Bind(conn net.Conn) (ok bool)
Bind binds the client. This is the second reply from the server.
No-op if the first reply is not decided, once it is, Bind can be called again.
Once r is accepted, r will wait for the decision on the second reply WITHOUT timeout, even if the connection to the client is closed.
type Binder ¶
type Binder struct { // Hostname of the server, not to be confused with listening address. // This is the address that will be sent in the first BND reply. // RFC 1928 states that the addresses in replies to BIND requests shall // be IP addresses, but a host name is considered valid here. // Do not modify this field when Binder is running. Hostname string // contains filtered or unexported fields }
Binder handles the BND requests.
Binder can listen for inbounds for different requests on the same port. Thus it is totally ok if you want to handle for multiple requests on one fixed port. Although, please note that, if Binder is not listening for any inbound on a port, the listener on that port will be closed, and will only be created again when next time Binder is required to listen on that port.
Currently if req is to be denied, only RepGeneralFailure will be replied.
func (*Binder) Handle ¶
Handle handles the BND request, blocks until error or successful bind. It can be called simultainously.
addr represents the address to listen at, and it can be empty, in this case Handle will listen on 0.0.0.0 and :: with one single system allocated port. If the host part in addr is a host name, Handle will resolve it to IP addresses and listen on all of them. If the port in addr is 0, Handle will try to use one single system allocated port for all of them.
Handle doesn't wait for the data transmission after the 2nd reply to finish.
timeout is disabled if it's 0.
type Capsulator ¶
type Capsulator interface { // Used for TCP connections. // MidLayer will call Write for encapsulation, and Read for decapsulation. // Connection is closed if non-nil error is returned. io.ReadWriter // Used for UDP packets. Packet is dropped if non-nil error is returned. // [MidLayer] doesn't actually call these methods, // Associator will call them though. EncapPacket(p []byte) ([]byte, error) DecapPacket(p []byte) ([]byte, error) }
Capsulator does encapsulation and decapsulation as corresponding auth method requires.
type CmdNotSupportedError ¶
type CmdNotSupportedError byte
func (CmdNotSupportedError) Error ¶
func (e CmdNotSupportedError) Error() string
func (CmdNotSupportedError) Is ¶
func (e CmdNotSupportedError) Is(target error) bool
Is returns true if target is ErrMalformed.
func (CmdNotSupportedError) Unwrap ¶
func (e CmdNotSupportedError) Unwrap() error
Unwrap returns errors.ErrUnsupported.
type ConnectRequest ¶
type ConnectRequest struct { Request // contains filtered or unexported fields }
type Handshake ¶
type Handshake struct {
// contains filtered or unexported fields
}
A Handshake represents the version identifier/method selection message. The message is called handshake in this entire module because...its full name is just too long. Handshake will be denied automatically if it's not accepted or denied after PeriodAutoDeny.
All methods of Handshake can be called simultainously.
func (*Handshake) Accept ¶
func (hs *Handshake) Accept(method byte, neg Subnegotiator) (ok bool)
Accept accepts the handshake, but also instead denies the request if params are invalid, e.g. when method is MethodNoAccepted.
Can be called only once, furthur calls are no-op.
func (*Handshake) Deny ¶
Deny denies the handshake by returning NO ACCEPTABLE METHODS.
Can be called only once, furthur calls are no-op.
func (*Handshake) Methods ¶
Methods returns client's supported auth methods. Methods might return 0 method, or include method code 0xFF if the client did send so.
func (*Handshake) RemoteAddr ¶
type LogEntry ¶
type LogEntry struct { Time time.Time // Timestamp Severity string // Severity of this error, one of severity constants Verbosity int // Used by debug entries, the higher the more verbose, starts from 0 Err error // Inner error }
A LogEntry contains time stamp, severity and corresponding error message.
The returned string of its Error func doesn't include timestamp and severity.
type MidLayer ¶
type MidLayer struct {
// contains filtered or unexported fields
}
A MidLayer is a SOCKS5 middle layer. See package description for detail.
All methods of MidLayer can be called simultaineously.
func (*MidLayer) Close ¶
Close closes all established connections. It's useful if you want to kill all sessions.
If a connection has failed to close, ml won't try to close it next time. errs contain errors returned by net.Conn.Close.
If errors occur, Close joins them with errors.Join and return the result.
func (*MidLayer) HandshakeChan ¶
All channel methods create a corresponding channel if not ever created. If no channel is created or the channel is full, corresponding handshakes are rejected by closing connection, instead of sending a reply.
func (*MidLayer) LogChan ¶
All channel methods create a corresponding channel if not ever created. If no channel is created or the channel is full, corresponding log entries are discarded.
func (*MidLayer) RequestChan ¶
RequestChan is guaranteed to return a channel that receives one of types *ConnectRequest, *BindRequest and *AssocRequest.
All channel methods create a corresponding channel if not ever created. If no channel is created or the channel is full, corresponding requests are rejected with RepGeneralFailure.
type NoAuthSubneg ¶
type NoAuthSubneg struct{}
A NoAuthSubneg is a Subnegotiator that does no negotiation at all. It's typically used for NO AUTHENTICATION.
func (NoAuthSubneg) Negotiate ¶
func (n NoAuthSubneg) Negotiate(rw io.ReadWriter) (Capsulator, error)
type NoCap ¶
type NoCap struct {
// contains filtered or unexported fields
}
NoCap is a Capsulator that doesn't encapsulate/decapsulate at all. It's used for NO AUTHENTICATION and Username/Password Authentication.
type OpError ¶
type OpError struct { Op string // E.g. "read handshake", "serve", "reply". LocalAddr net.Addr RemoteAddr net.Addr Err error // Inner error }
An OpError contains Op string describing in which operation has the error occured. Currently the error util of socksy5 is not well designed, so OpError is for now just for the convenience of converting errors to strings.
type RelayError ¶
type RelayError struct { ClientRemoteAddr net.Addr ClientLocalAddr net.Addr HostRemoteAddr net.Addr HostLocalAddr net.Addr Client2HostErr error Host2ClientErr error }
A RelayError represents errors and address info of TCP traffic relaying for CONNECT and BIND requests.
func (*RelayError) Error ¶
func (e *RelayError) Error() string
func (*RelayError) Unwrap ¶
func (e *RelayError) Unwrap() (errs []error)
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
A Request represents a client request. Use ConnectRequest, BindRequest and AssocRequest for handling.
Accept / Deny methods of different request types can be called only once. Furthur calls are no-op and return ok being false.
Requests are denied if params passed to Accept funcs are invalid, e.g. addr string doesn't contain a port number, net.Addr returned by conn params is invalid. However, port 0 is considered valid and will be sent as is.
A Request will be denied automatically if it's not accepted or denied after PeriodAutoDeny, with exception being BindRequest, see BindRequest.Bind.
All methods of all request types can be called simultainously.
func (*Request) Capsulation ¶
func (r *Request) Capsulation() Capsulator
Capsulation returns the Capsulator in use.
func (*Request) Deny ¶
Deny denies the request with REP byte code. If rep is RepSucceeded, it's replaced by RepGeneralFailure.
addr is used for BND fields. If addr is invalid, BND.ADDR will be set to empty domain name, and BND.PORT will be set to 0.
func (*Request) RemoteAddr ¶
type RequestNotHandledError ¶
type RequestNotHandledError struct { Type string // One of "handshake", "CONNECT", "BIND", "UDP ASSOCIATE" Timeout bool // If the request is not handled in a duration of PeriodAutoDeny }
A RequestNotHandledError can be received from the error channel when a handshake or request is not handled by external code.
func (*RequestNotHandledError) Error ¶
func (e *RequestNotHandledError) Error() string
type RsvViolationError ¶
type RsvViolationError byte
func (RsvViolationError) Error ¶
func (e RsvViolationError) Error() string
func (RsvViolationError) Is ¶
func (e RsvViolationError) Is(target error) bool
Is returns true if target is ErrMalformed.
type Subnegotiator ¶
type Subnegotiator interface { Negotiate(io.ReadWriter) (Capsulator, error) Type() string }
Subnegotiator does subnegotiation after an auth method has been chosen.
When subnegotiation begins, MidLayer will pass net.Conn to Negotiate. Implementation should retain the ReadWriter for capsulation use. If nil Capsulator is returned, NoCap is used instead. Connection is closed if non-nil error is returned.
type UsrPwdSubneg ¶
type UsrPwdSubneg struct { // List of username password pair. // A list entry is to be ignored if its number of elements is not 2. List [][][]byte }
A UsrPwdSubneg is a Subnegotiator for Username/Password Authentication. Implements RFC 1929.
func (UsrPwdSubneg) Negotiate ¶
func (n UsrPwdSubneg) Negotiate(rw io.ReadWriter) (c Capsulator, err error)
type UsrPwdVerIncorrectError ¶
type UsrPwdVerIncorrectError byte
func (UsrPwdVerIncorrectError) Error ¶
func (e UsrPwdVerIncorrectError) Error() string
func (UsrPwdVerIncorrectError) Is ¶
func (e UsrPwdVerIncorrectError) Is(target error) bool
Is returns true if target is ErrMalformed.
type VerIncorrectError ¶
type VerIncorrectError byte
func (VerIncorrectError) Error ¶
func (e VerIncorrectError) Error() string
func (VerIncorrectError) Is ¶
func (e VerIncorrectError) Is(target error) bool
Is returns true if target is ErrMalformed.