go-socks5/server.go

181 lines
5.3 KiB
Go
Raw Normal View History

2014-01-23 19:28:30 +00:00
package socks5
import (
"bufio"
2020-04-19 09:08:22 +00:00
"context"
"errors"
2014-01-23 19:28:30 +00:00
"fmt"
2020-04-22 02:15:40 +00:00
"io"
2020-04-19 09:47:39 +00:00
"io/ioutil"
2014-01-23 19:28:30 +00:00
"log"
"net"
2020-08-05 05:17:05 +00:00
"github.com/thinkgos/go-socks5/statute"
2014-01-23 19:28:30 +00:00
)
2020-04-20 08:52:29 +00:00
// GPool is used to implement custom goroutine pool default use goroutine
type GPool interface {
Submit(f func()) error
}
2020-04-20 13:17:38 +00:00
// Server is reponsible for accepting connections and handling
// the details of the SOCKS5 protocol
type Server struct {
authMethods map[uint8]Authenticator
2014-02-18 14:21:03 +00:00
// AuthMethods can be provided to implement custom authentication
// By default, "auth-less" mode is enabled.
// For password-based auth use UserPassAuthenticator.
2020-04-20 13:17:38 +00:00
authCustomMethods []Authenticator
// If provided, username/password authentication is enabled,
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
// and AUthMethods is nil, then "auth-less" mode is enabled.
2020-04-20 13:17:38 +00:00
credentials CredentialStore
// resolver can be provided to do custom name resolution.
2014-01-23 19:28:30 +00:00
// Defaults to DNSResolver if not provided.
2020-04-20 13:17:38 +00:00
resolver NameResolver
// rules is provided to enable custom logic around permitting
2020-08-05 06:40:07 +00:00
// various commands. If not provided, NewPermitAll is used.
2020-04-20 13:17:38 +00:00
rules RuleSet
// rewriter can be used to transparently rewrite addresses.
2014-01-24 00:55:08 +00:00
// This is invoked before the RuleSet is invoked.
// Defaults to NoRewrite.
2020-04-20 13:17:38 +00:00
rewriter AddressRewriter
// bindIP is used for bind or udp associate
bindIP net.IP
// logger can be used to provide a custom log target.
2020-04-19 09:47:39 +00:00
// Defaults to ioutil.Discard.
2020-04-20 13:17:38 +00:00
logger Logger
2016-02-04 19:25:27 +00:00
// Optional function for dialing out
2020-04-20 13:17:38 +00:00
dial func(ctx context.Context, network, addr string) (net.Conn, error)
// buffer pool
2020-08-06 02:12:46 +00:00
bufferPool BufferPool
2020-04-20 13:17:38 +00:00
// goroutine pool
gPool GPool
2020-04-22 02:15:40 +00:00
// user's handle
2020-04-24 07:52:47 +00:00
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
2014-01-23 19:28:30 +00:00
}
2020-08-05 09:32:37 +00:00
// NewServer creates a new Server and potentially returns an error
func NewServer(opts ...Option) *Server {
2020-04-20 13:17:38 +00:00
server := &Server{
authMethods: make(map[uint8]Authenticator),
authCustomMethods: []Authenticator{&NoAuthAuthenticator{}},
2020-08-06 02:12:46 +00:00
bufferPool: NewPool(32 * 1024),
2020-04-20 13:17:38 +00:00
resolver: DNSResolver{},
2020-08-05 06:40:07 +00:00
rules: NewPermitAll(),
2020-04-20 13:17:38 +00:00
logger: NewLogger(log.New(ioutil.Discard, "socks5: ", log.LstdFlags)),
dial: func(ctx context.Context, net_, addr string) (net.Conn, error) {
2020-04-20 08:19:51 +00:00
return net.Dial(net_, addr)
2020-04-20 13:17:38 +00:00
},
2020-04-20 08:19:51 +00:00
}
2020-04-20 13:17:38 +00:00
for _, opt := range opts {
opt(server)
2014-01-23 19:28:30 +00:00
}
2014-02-18 14:21:03 +00:00
2020-04-20 13:17:38 +00:00
// Ensure we have at least one authentication method enabled
if len(server.authCustomMethods) == 0 && server.credentials != nil {
server.authCustomMethods = []Authenticator{&UserPassAuthenticator{server.credentials}}
}
2014-02-18 14:21:03 +00:00
2020-04-20 13:17:38 +00:00
for _, v := range server.authCustomMethods {
server.authMethods[v.GetCode()] = v
2014-02-18 14:21:03 +00:00
}
2020-04-20 13:17:38 +00:00
return server
2014-01-23 19:28:30 +00:00
}
// ListenAndServe is used to create a listener and serve on it
func (s *Server) ListenAndServe(network, addr string) error {
l, err := net.Listen(network, addr)
if err != nil {
return err
}
return s.Serve(l)
}
// Serve is used to serve connections from a listener
func (s *Server) Serve(l net.Listener) error {
for {
conn, err := l.Accept()
if err != nil {
return err
}
2020-04-20 08:52:29 +00:00
s.submit(func() {
2020-08-06 01:32:19 +00:00
if err := s.ServeConn(conn); err != nil {
2020-04-22 02:32:03 +00:00
s.logger.Errorf("server conn %v", err)
}
2020-04-20 08:52:29 +00:00
})
2014-01-23 19:28:30 +00:00
}
}
// ServeConn is used to serve a single connection.
2020-08-05 02:34:29 +00:00
func (s *Server) ServeConn(conn net.Conn) error {
2020-08-05 07:27:22 +00:00
var authContext *AuthContext
2014-01-23 19:28:30 +00:00
defer conn.Close()
2014-01-23 19:28:30 +00:00
bufConn := bufio.NewReader(conn)
2020-08-05 05:54:05 +00:00
mr, err := statute.ParseMethodRequest(bufConn)
if err != nil {
2014-01-23 19:28:30 +00:00
return err
}
2020-08-05 05:54:05 +00:00
if mr.Ver != statute.VersionSocks5 {
return statute.ErrNotSupportVersion
}
// Authenticate the connection
authContext, err = s.authenticate(conn, bufConn, conn.RemoteAddr().String(), mr.Methods)
if err != nil {
2020-08-05 07:27:22 +00:00
return fmt.Errorf("failed to authenticate: %w", err)
2014-01-23 19:28:30 +00:00
}
2020-08-05 02:34:29 +00:00
// 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 {
2020-08-05 07:27:22 +00:00
return fmt.Errorf("failed to send reply %w", err)
}
}
2020-08-05 07:27:22 +00:00
return fmt.Errorf("failed to read destination address, %w", err)
}
2020-08-05 07:27:22 +00:00
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)
}
2020-08-05 07:27:22 +00:00
request.AuthContext = authContext
2020-04-23 02:16:26 +00:00
request.LocalAddr = conn.LocalAddr()
2020-04-23 02:15:02 +00:00
request.RemoteAddr = conn.RemoteAddr()
2014-01-23 19:28:30 +00:00
// Process the client request
return s.handleRequest(conn, request)
2014-01-23 19:28:30 +00:00
}
2020-04-20 08:52:29 +00:00
2020-08-05 06:40:07 +00:00
// authenticate is used to handle connection authentication
func (s *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 := s.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
}
2020-04-20 08:52:29 +00:00
func (s *Server) submit(f func()) {
if s.gPool == nil || s.gPool.Submit(f) != nil {
go f()
}
}