blob: 973f57f19703995561037a3f9f6c84d0f5c5048a [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package proxy
6
7import (
8 "errors"
9 "io"
10 "net"
11 "strconv"
12)
13
14// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
15// with an optional username and password. See RFC 1928.
16func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
17 s := &socks5{
18 network: network,
19 addr: addr,
20 forward: forward,
21 }
22 if auth != nil {
23 s.user = auth.User
24 s.password = auth.Password
25 }
26
27 return s, nil
28}
29
30type socks5 struct {
31 user, password string
32 network, addr string
33 forward Dialer
34}
35
36const socks5Version = 5
37
38const (
39 socks5AuthNone = 0
40 socks5AuthPassword = 2
41)
42
43const socks5Connect = 1
44
45const (
46 socks5IP4 = 1
47 socks5Domain = 3
48 socks5IP6 = 4
49)
50
51var socks5Errors = []string{
52 "",
53 "general failure",
54 "connection forbidden",
55 "network unreachable",
56 "host unreachable",
57 "connection refused",
58 "TTL expired",
59 "command not supported",
60 "address type not supported",
61}
62
63// Dial connects to the address addr on the network net via the SOCKS5 proxy.
64func (s *socks5) Dial(network, addr string) (net.Conn, error) {
65 switch network {
66 case "tcp", "tcp6", "tcp4":
67 default:
68 return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
69 }
70
71 conn, err := s.forward.Dial(s.network, s.addr)
72 if err != nil {
73 return nil, err
74 }
75 if err := s.connect(conn, addr); err != nil {
76 conn.Close()
77 return nil, err
78 }
79 return conn, nil
80}
81
82// connect takes an existing connection to a socks5 proxy server,
83// and commands the server to extend that connection to target,
84// which must be a canonical address with a host and port.
85func (s *socks5) connect(conn net.Conn, target string) error {
86 host, portStr, err := net.SplitHostPort(target)
87 if err != nil {
88 return err
89 }
90
91 port, err := strconv.Atoi(portStr)
92 if err != nil {
93 return errors.New("proxy: failed to parse port number: " + portStr)
94 }
95 if port < 1 || port > 0xffff {
96 return errors.New("proxy: port number out of range: " + portStr)
97 }
98
99 // the size here is just an estimate
100 buf := make([]byte, 0, 6+len(host))
101
102 buf = append(buf, socks5Version)
103 if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
104 buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
105 } else {
106 buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
107 }
108
109 if _, err := conn.Write(buf); err != nil {
110 return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
111 }
112
113 if _, err := io.ReadFull(conn, buf[:2]); err != nil {
114 return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
115 }
116 if buf[0] != 5 {
117 return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
118 }
119 if buf[1] == 0xff {
120 return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
121 }
122
123 if buf[1] == socks5AuthPassword {
124 buf = buf[:0]
125 buf = append(buf, 1 /* password protocol version */)
126 buf = append(buf, uint8(len(s.user)))
127 buf = append(buf, s.user...)
128 buf = append(buf, uint8(len(s.password)))
129 buf = append(buf, s.password...)
130
131 if _, err := conn.Write(buf); err != nil {
132 return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
133 }
134
135 if _, err := io.ReadFull(conn, buf[:2]); err != nil {
136 return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
137 }
138
139 if buf[1] != 0 {
140 return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
141 }
142 }
143
144 buf = buf[:0]
145 buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
146
147 if ip := net.ParseIP(host); ip != nil {
148 if ip4 := ip.To4(); ip4 != nil {
149 buf = append(buf, socks5IP4)
150 ip = ip4
151 } else {
152 buf = append(buf, socks5IP6)
153 }
154 buf = append(buf, ip...)
155 } else {
156 if len(host) > 255 {
157 return errors.New("proxy: destination hostname too long: " + host)
158 }
159 buf = append(buf, socks5Domain)
160 buf = append(buf, byte(len(host)))
161 buf = append(buf, host...)
162 }
163 buf = append(buf, byte(port>>8), byte(port))
164
165 if _, err := conn.Write(buf); err != nil {
166 return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
167 }
168
169 if _, err := io.ReadFull(conn, buf[:4]); err != nil {
170 return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
171 }
172
173 failure := "unknown error"
174 if int(buf[1]) < len(socks5Errors) {
175 failure = socks5Errors[buf[1]]
176 }
177
178 if len(failure) > 0 {
179 return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
180 }
181
182 bytesToDiscard := 0
183 switch buf[3] {
184 case socks5IP4:
185 bytesToDiscard = net.IPv4len
186 case socks5IP6:
187 bytesToDiscard = net.IPv6len
188 case socks5Domain:
189 _, err := io.ReadFull(conn, buf[:1])
190 if err != nil {
191 return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
192 }
193 bytesToDiscard = int(buf[0])
194 default:
195 return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
196 }
197
198 if cap(buf) < bytesToDiscard {
199 buf = make([]byte, bytesToDiscard)
200 } else {
201 buf = buf[:bytesToDiscard]
202 }
203 if _, err := io.ReadFull(conn, buf); err != nil {
204 return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
205 }
206
207 // Also need to discard the port number
208 if _, err := io.ReadFull(conn, buf[:2]); err != nil {
209 return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
210 }
211
212 return nil
213}