1
2
3
4
5
6
7
8
9
10 package socks
11
12 import (
13 "context"
14 "errors"
15 "io"
16 "net"
17 "strconv"
18 )
19
20
21 type Command int
22
23 func (cmd Command) String() string {
24 switch cmd {
25 case CmdConnect:
26 return "socks connect"
27 case cmdBind:
28 return "socks bind"
29 default:
30 return "socks " + strconv.Itoa(int(cmd))
31 }
32 }
33
34
35 type AuthMethod int
36
37
38 type Reply int
39
40 func (code Reply) String() string {
41 switch code {
42 case StatusSucceeded:
43 return "succeeded"
44 case 0x01:
45 return "general SOCKS server failure"
46 case 0x02:
47 return "connection not allowed by ruleset"
48 case 0x03:
49 return "network unreachable"
50 case 0x04:
51 return "host unreachable"
52 case 0x05:
53 return "connection refused"
54 case 0x06:
55 return "TTL expired"
56 case 0x07:
57 return "command not supported"
58 case 0x08:
59 return "address type not supported"
60 default:
61 return "unknown code: " + strconv.Itoa(int(code))
62 }
63 }
64
65
66 const (
67 Version5 = 0x05
68
69 AddrTypeIPv4 = 0x01
70 AddrTypeFQDN = 0x03
71 AddrTypeIPv6 = 0x04
72
73 CmdConnect Command = 0x01
74 cmdBind Command = 0x02
75
76 AuthMethodNotRequired AuthMethod = 0x00
77 AuthMethodUsernamePassword AuthMethod = 0x02
78 AuthMethodNoAcceptableMethods AuthMethod = 0xff
79
80 StatusSucceeded Reply = 0x00
81 )
82
83
84
85 type Addr struct {
86 Name string
87 IP net.IP
88 Port int
89 }
90
91 func (a *Addr) Network() string { return "socks" }
92
93 func (a *Addr) String() string {
94 if a == nil {
95 return "<nil>"
96 }
97 port := strconv.Itoa(a.Port)
98 if a.IP == nil {
99 return net.JoinHostPort(a.Name, port)
100 }
101 return net.JoinHostPort(a.IP.String(), port)
102 }
103
104
105 type Conn struct {
106 net.Conn
107
108 boundAddr net.Addr
109 }
110
111
112
113 func (c *Conn) BoundAddr() net.Addr {
114 if c == nil {
115 return nil
116 }
117 return c.boundAddr
118 }
119
120
121 type Dialer struct {
122 cmd Command
123 proxyNetwork string
124 proxyAddress string
125
126
127
128 ProxyDial func(context.Context, string, string) (net.Conn, error)
129
130
131
132
133 AuthMethods []AuthMethod
134
135
136
137
138 Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
139 }
140
141
142
143
144
145
146
147
148
149
150
151 func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
152 if err := d.validateTarget(network, address); err != nil {
153 proxy, dst, _ := d.pathAddrs(address)
154 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
155 }
156 if ctx == nil {
157 proxy, dst, _ := d.pathAddrs(address)
158 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
159 }
160 var err error
161 var c net.Conn
162 if d.ProxyDial != nil {
163 c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
164 } else {
165 var dd net.Dialer
166 c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
167 }
168 if err != nil {
169 proxy, dst, _ := d.pathAddrs(address)
170 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
171 }
172 a, err := d.connect(ctx, c, address)
173 if err != nil {
174 c.Close()
175 proxy, dst, _ := d.pathAddrs(address)
176 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
177 }
178 return &Conn{Conn: c, boundAddr: a}, nil
179 }
180
181
182
183
184
185
186
187 func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) {
188 if err := d.validateTarget(network, address); err != nil {
189 proxy, dst, _ := d.pathAddrs(address)
190 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
191 }
192 if ctx == nil {
193 proxy, dst, _ := d.pathAddrs(address)
194 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
195 }
196 a, err := d.connect(ctx, c, address)
197 if err != nil {
198 proxy, dst, _ := d.pathAddrs(address)
199 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
200 }
201 return a, nil
202 }
203
204
205
206
207
208
209
210 func (d *Dialer) Dial(network, address string) (net.Conn, error) {
211 if err := d.validateTarget(network, address); err != nil {
212 proxy, dst, _ := d.pathAddrs(address)
213 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
214 }
215 var err error
216 var c net.Conn
217 if d.ProxyDial != nil {
218 c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress)
219 } else {
220 c, err = net.Dial(d.proxyNetwork, d.proxyAddress)
221 }
222 if err != nil {
223 proxy, dst, _ := d.pathAddrs(address)
224 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
225 }
226 if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil {
227 c.Close()
228 return nil, err
229 }
230 return c, nil
231 }
232
233 func (d *Dialer) validateTarget(network, address string) error {
234 switch network {
235 case "tcp", "tcp6", "tcp4":
236 default:
237 return errors.New("network not implemented")
238 }
239 switch d.cmd {
240 case CmdConnect, cmdBind:
241 default:
242 return errors.New("command not implemented")
243 }
244 return nil
245 }
246
247 func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
248 for i, s := range []string{d.proxyAddress, address} {
249 host, port, err := splitHostPort(s)
250 if err != nil {
251 return nil, nil, err
252 }
253 a := &Addr{Port: port}
254 a.IP = net.ParseIP(host)
255 if a.IP == nil {
256 a.Name = host
257 }
258 if i == 0 {
259 proxy = a
260 } else {
261 dst = a
262 }
263 }
264 return
265 }
266
267
268
269 func NewDialer(network, address string) *Dialer {
270 return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
271 }
272
273 const (
274 authUsernamePasswordVersion = 0x01
275 authStatusSucceeded = 0x00
276 )
277
278
279
280 type UsernamePassword struct {
281 Username string
282 Password string
283 }
284
285
286
287 func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
288 switch auth {
289 case AuthMethodNotRequired:
290 return nil
291 case AuthMethodUsernamePassword:
292 if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) > 255 {
293 return errors.New("invalid username/password")
294 }
295 b := []byte{authUsernamePasswordVersion}
296 b = append(b, byte(len(up.Username)))
297 b = append(b, up.Username...)
298 b = append(b, byte(len(up.Password)))
299 b = append(b, up.Password...)
300
301
302 if _, err := rw.Write(b); err != nil {
303 return err
304 }
305 if _, err := io.ReadFull(rw, b[:2]); err != nil {
306 return err
307 }
308 if b[0] != authUsernamePasswordVersion {
309 return errors.New("invalid username/password version")
310 }
311 if b[1] != authStatusSucceeded {
312 return errors.New("username/password authentication failed")
313 }
314 return nil
315 }
316 return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
317 }
318
View as plain text