-
Notifications
You must be signed in to change notification settings - Fork 69
/
server.go
199 lines (178 loc) · 5.79 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package socks5
import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log"
"net"
"github.com/things-go/go-socks5/bufferpool"
"github.com/things-go/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 can be provided to implement authentication
// By default, "no-auth" mode is enabled.
// For password-based auth use UserPassAuthenticator.
authMethods []Authenticator
// If provided, username/password authentication is enabled,
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
// and authMethods 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 io.Discard.
logger Logger
// Optional function for dialing out.
// The callback set by dialWithRequest will be called first.
dial func(ctx context.Context, network, addr string) (net.Conn, error)
// Optional function for dialing out with the access of request detail.
dialWithRequest func(ctx context.Context, network, addr string, request *Request) (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
// user's middleware
userConnectMiddlewares MiddlewareChain
userBindMiddlewares MiddlewareChain
userAssociateMiddlewares MiddlewareChain
}
// NewServer creates a new Server
func NewServer(opts ...Option) *Server {
srv := &Server{
authMethods: []Authenticator{},
bufferPool: bufferpool.NewPool(32 * 1024),
resolver: DNSResolver{},
rules: NewPermitAll(),
logger: NewLogger(log.New(io.Discard, "socks5: ", log.LstdFlags)),
}
for _, opt := range opts {
opt(srv)
}
// Ensure we have at least one authentication method enabled
if (len(srv.authMethods) == 0) && srv.credentials != nil {
srv.authMethods = []Authenticator{&UserPassAuthenticator{srv.credentials}}
}
if len(srv.authMethods) == 0 {
srv.authMethods = []Authenticator{&NoAuthAuthenticator{}}
}
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)
}
// ListenAndServeTLS is used to create a TLS listener and serve on it
func (sf *Server) ListenAndServeTLS(network, addr string, c *tls.Config) error {
l, err := tls.Listen(network, addr, c)
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
userAddr := ""
if conn.RemoteAddr() != nil {
userAddr = conn.RemoteAddr().String()
}
authContext, err = sf.authenticate(conn, bufConn, userAddr, 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 _, auth := range sf.authMethods {
for _, method := range methods {
if auth.GetCode() == method {
return auth.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()
}
}