go-socks5/server.go

188 lines
5.4 KiB
Go

package socks5
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"git.tcp.direct/kayos/go-socks5/bufferpool"
"git.tcp.direct/kayos/go-socks5/statute"
)
// GPool is used to implement custom goroutine pool default use goroutine
type GPool interface {
Submit(f func()) error
}
// Server is responsible for accepting connections and handling
// the details of the SOCKS5 protocol
type Server struct {
authMethods map[uint8]Authenticator
// AuthMethods can be provided to implement custom authentication
// By default, "no-auth" mode is enabled.
// For password-based auth use UserPassAuthenticator.
authCustomMethods []Authenticator
// If provided, username/password authentication is enabled,
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
// and authCustomMethods is nil, then "no-auth" mode is enabled.
credentials CredentialStore
// resolver can be provided to do custom name resolution.
// Defaults to DNSResolver if not provided.
resolver NameResolver
// rules is provided to enable custom logic around permitting
// various commands. If not provided, NewPermitAll is used.
rules RuleSet
// rewriter can be used to transparently rewrite addresses.
// This is invoked before the RuleSet is invoked.
// Defaults to NoRewrite.
rewriter AddressRewriter
// bindIP is used for bind or udp associate
bindIP net.IP
// logger can be used to provide a custom log target.
// Defaults to ioutil.Discard.
logger Logger
// Optional function for dialing out
dial func(ctx context.Context, network, addr string) (net.Conn, error)
// buffer pool
bufferPool bufferpool.BufPool
// goroutine pool
gPool GPool
// user's handle
userConnectHandle func(ctx context.Context, writer io.Writer, request *Request) error
userBindHandle func(ctx context.Context, writer io.Writer, request *Request) error
userAssociateHandle func(ctx context.Context, writer io.Writer, request *Request) error
}
// NewServer creates a new Server
func NewServer(opts ...Option) *Server {
srv := &Server{
authMethods: make(map[uint8]Authenticator),
authCustomMethods: []Authenticator{},
bufferPool: bufferpool.NewPool(32 * 1024),
resolver: DNSResolver{},
rules: NewPermitAll(),
logger: NewLogger(log.New(ioutil.Discard, "socks5: ", log.LstdFlags)),
dial: func(ctx context.Context, net_, addr string) (net.Conn, error) {
return net.Dial(net_, addr)
},
}
for _, opt := range opts {
opt(srv)
}
// Ensure we have at least one authentication method enabled
if (len(srv.authCustomMethods) == 0) && srv.credentials != nil {
srv.authCustomMethods = []Authenticator{&UserPassAuthenticator{srv.credentials}}
}
if len(srv.authCustomMethods) == 0 {
srv.authCustomMethods = []Authenticator{&NoAuthAuthenticator{}}
}
for _, v := range srv.authCustomMethods {
srv.authMethods[v.GetCode()] = v
}
return srv
}
// ListenAndServe is used to create a listener and serve on it
func (sf *Server) ListenAndServe(network, addr string) error {
l, err := net.Listen(network, addr)
if err != nil {
return err
}
return sf.Serve(l)
}
// Serve is used to serve connections from a listener
func (sf *Server) Serve(l net.Listener) error {
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
return err
}
sf.goFunc(func() {
if err := sf.ServeConn(conn); err != nil {
sf.logger.Errorf("server: %v", err)
}
})
}
}
// ServeConn is used to serve a single connection.
func (sf *Server) ServeConn(conn net.Conn) error {
var authContext *AuthContext
defer conn.Close()
bufConn := bufio.NewReader(conn)
mr, err := statute.ParseMethodRequest(bufConn)
if err != nil {
return err
}
if mr.Ver != statute.VersionSocks5 {
return statute.ErrNotSupportVersion
}
// Authenticate the connection
authContext, err = sf.authenticate(conn, bufConn, conn.RemoteAddr().String(), mr.Methods)
if err != nil {
return fmt.Errorf("failed to authenticate: %w", err)
}
// The client request detail
request, err := ParseRequest(bufConn)
if err != nil {
if errors.Is(err, statute.ErrUnrecognizedAddrType) {
if err := SendReply(conn, statute.RepAddrTypeNotSupported, nil); err != nil {
return fmt.Errorf("failed to send reply %w", err)
}
}
return fmt.Errorf("failed to read destination address, %w", err)
}
if request.Request.Command != statute.CommandConnect &&
request.Request.Command != statute.CommandBind &&
request.Request.Command != statute.CommandAssociate {
if err := SendReply(conn, statute.RepCommandNotSupported, nil); err != nil {
return fmt.Errorf("failed to send reply, %v", err)
}
return fmt.Errorf("unrecognized command[%d]", request.Request.Command)
}
request.AuthContext = authContext
request.LocalAddr = conn.LocalAddr()
request.RemoteAddr = conn.RemoteAddr()
// Process the client request
return sf.handleRequest(conn, request)
}
// authenticate is used to handle connection authentication
func (sf *Server) authenticate(conn io.Writer, bufConn io.Reader,
userAddr string, methods []byte) (*AuthContext, error) {
// Select a usable method
for _, method := range methods {
if cator, found := sf.authMethods[method]; found {
return cator.Authenticate(bufConn, conn, userAddr)
}
}
// No usable method found
conn.Write([]byte{statute.VersionSocks5, statute.MethodNoAcceptable}) // nolint: errcheck
return nil, statute.ErrNoSupportedAuth
}
func (sf *Server) goFunc(f func()) {
if sf.gPool == nil || sf.gPool.Submit(f) != nil {
go f()
}
}