187 lines
5.4 KiB
Go
187 lines
5.4 KiB
Go
package socks5
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
|
|
"github.com/thinkgos/go-socks5/bufferpool"
|
|
"github.com/thinkgos/go-socks5/statute"
|
|
)
|
|
|
|
// GPool is used to implement custom goroutine pool default use goroutine
|
|
type GPool interface {
|
|
Submit(f func()) error
|
|
}
|
|
|
|
// Server is reponsible 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() // nolint: errcheck
|
|
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()
|
|
}
|
|
}
|