1 // Copyright 2012 Samuel Stauffer. All rights reserved.
2 // Use of this source code is governed by a 3-clause BSD
3 // license that can be found in the LICENSE file.
8 - GSS-API authentication is not supported
9 - only SOCKS version 5 is supported
10 - TCP bind and UDP not yet supported
12 Example http client over SOCKS5:
14 proxy := &socks.Proxy{"127.0.0.1:1080"}
15 tr := &http.Transport{
18 client := &http.Client{Transport: tr}
19 resp, err := client.Get("https://example.com")
40 authUsernamePassword = 2
41 authUnavailable = 0xff
45 commandUdpAssociate = 3
51 statusRequestGranted = 0
52 statusGeneralFailure = 1
53 statusConnectionNotAllowed = 2
54 statusNetworkUnreachable = 3
55 statusHostUnreachable = 4
56 statusConnectionRefused = 5
58 statusCommandNotSupport = 7
59 statusAddressTypeNotSupported = 8
63 ErrAuthFailed = errors.New("authentication failed")
64 ErrInvalidProxyResponse = errors.New("invalid proxy response")
65 ErrNoAcceptableAuthMethod = errors.New("no acceptable authentication method")
67 statusErrors = map[byte]error{
68 statusGeneralFailure: errors.New("general failure"),
69 statusConnectionNotAllowed: errors.New("connection not allowed by ruleset"),
70 statusNetworkUnreachable: errors.New("network unreachable"),
71 statusHostUnreachable: errors.New("host unreachable"),
72 statusConnectionRefused: errors.New("connection refused by destination host"),
73 statusTtlExpired: errors.New("TTL expired"),
74 statusCommandNotSupport: errors.New("command not supported / protocol error"),
75 statusAddressTypeNotSupported: errors.New("address type not supported"),
86 func (p *Proxy) Dial(network, addr string) (net.Conn, error) {
87 return p.dial(network, addr, 0)
90 func (p *Proxy) DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) {
91 return p.dial(network, addr, timeout)
94 func (p *Proxy) dial(network, addr string, timeout time.Duration) (net.Conn, error) {
95 host, strPort, err := net.SplitHostPort(addr)
99 port, err := strconv.Atoi(strPort)
104 conn, err := net.DialTimeout("tcp", p.Addr, timeout)
109 var user, pass string
112 _, err := io.ReadFull(rand.Reader, b[:])
117 user = hex.EncodeToString(b[0:8])
118 pass = hex.EncodeToString(b[8:16])
123 buf := make([]byte, 32+len(host)+len(user)+len(pass))
126 buf[0] = protocolVersion
129 buf[1] = 2 // num auth methods
131 buf[3] = authUsernamePassword
134 buf[1] = 1 // num auth methods
138 _, err = conn.Write(buf)
144 // Server's auth choice
146 if _, err := io.ReadFull(conn, buf[:2]); err != nil {
150 if buf[0] != protocolVersion {
152 return nil, ErrInvalidProxyResponse
157 err = ErrInvalidProxyResponse
158 case authUnavailable:
159 err = ErrNoAcceptableAuthMethod
161 err = ErrNoAcceptableAuthMethod
162 case authUsernamePassword:
163 buf = buf[:3+len(user)+len(pass)]
164 buf[0] = 1 // version
165 buf[1] = byte(len(user))
167 buf[2+len(user)] = byte(len(pass))
168 copy(buf[3+len(user):], pass)
169 if _, err = conn.Write(buf); err != nil {
173 if _, err = io.ReadFull(conn, buf[:2]); err != nil {
177 if buf[0] != 1 { // version
178 err = ErrInvalidProxyResponse
179 } else if buf[1] != 0 { // 0 = succes, else auth failed
190 // Command / connection request
192 buf = buf[:7+len(host)]
193 buf[0] = protocolVersion
194 buf[1] = commandTcpConnect
195 buf[2] = 0 // reserved
196 buf[3] = addressTypeDomain
197 buf[4] = byte(len(host))
199 buf[5+len(host)] = byte(port >> 8)
200 buf[6+len(host)] = byte(port & 0xff)
201 if _, err := conn.Write(buf); err != nil {
208 if _, err := io.ReadFull(conn, buf[:4]); err != nil {
213 if buf[0] != protocolVersion {
215 return nil, ErrInvalidProxyResponse
218 if buf[1] != statusRequestGranted {
220 err := statusErrors[buf[1]]
222 err = ErrInvalidProxyResponse
227 paddr := &ProxiedAddr{Net: network}
232 return nil, ErrInvalidProxyResponse
233 case addressTypeIPv4:
234 if _, err := io.ReadFull(conn, buf[:4]); err != nil {
238 paddr.Host = net.IP(buf).String()
239 case addressTypeIPv6:
240 if _, err := io.ReadFull(conn, buf[:16]); err != nil {
244 paddr.Host = net.IP(buf).String()
245 case addressTypeDomain:
246 if _, err := io.ReadFull(conn, buf[:1]); err != nil {
251 if _, err := io.ReadFull(conn, buf[:domainLen]); err != nil {
255 paddr.Host = string(buf[:domainLen])
258 if _, err := io.ReadFull(conn, buf[:2]); err != nil {
262 paddr.Port = int(buf[0])<<8 | int(buf[1])
267 remoteAddr: &ProxiedAddr{network, host, port},