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 { 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() } }