blob: 41a3affbcfc7bd8fcd122a8816adda8c580391c6 [file] [log] [blame]
Adam Langleyc9064102012-08-07 12:02:26 -04001// 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 break
68 default:
69 return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
70 }
71
72 conn, err := s.forward.Dial(s.network, s.addr)
73 if err != nil {
74 return nil, err
75 }
76 closeConn := &conn
77 defer func() {
78 if closeConn != nil {
79 (*closeConn).Close()
80 }
81 }()
82
83 host, portStr, err := net.SplitHostPort(addr)
84 if err != nil {
85 return nil, err
86 }
87
88 port, err := strconv.Atoi(portStr)
89 if err != nil {
90 return nil, errors.New("proxy: failed to parse port number: " + portStr)
91 }
92 if port < 1 || port > 0xffff {
93 return nil, errors.New("proxy: port number out of range: " + portStr)
94 }
95
96 // the size here is just an estimate
97 buf := make([]byte, 0, 6+len(host))
98
99 buf = append(buf, socks5Version)
100 if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
101 buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
102 } else {
103 buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
104 }
105
106 if _, err = conn.Write(buf); err != nil {
107 return nil, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
108 }
109
110 if _, err = io.ReadFull(conn, buf[:2]); err != nil {
111 return nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
112 }
113 if buf[0] != 5 {
114 return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
115 }
116 if buf[1] == 0xff {
117 return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
118 }
119
120 if buf[1] == socks5AuthPassword {
121 buf = buf[:0]
122 buf = append(buf, socks5Version)
123 buf = append(buf, uint8(len(s.user)))
124 buf = append(buf, s.user...)
125 buf = append(buf, uint8(len(s.password)))
126 buf = append(buf, s.password...)
127
128 if _, err = conn.Write(buf); err != nil {
129 return nil, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
130 }
131
132 if _, err = io.ReadFull(conn, buf[:2]); err != nil {
133 return nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
134 }
135
136 if buf[1] != 0 {
137 return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
138 }
139 }
140
141 buf = buf[:0]
142 buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
143
144 if ip := net.ParseIP(host); ip != nil {
Mikio Hara3805a432012-11-17 14:35:42 +0900145 if ip.To4() != nil {
Adam Langleyc9064102012-08-07 12:02:26 -0400146 buf = append(buf, socks5IP4)
147 } else {
148 buf = append(buf, socks5IP6)
149 }
150 buf = append(buf, []byte(ip)...)
151 } else {
152 buf = append(buf, socks5Domain)
153 buf = append(buf, byte(len(host)))
154 buf = append(buf, host...)
155 }
156 buf = append(buf, byte(port>>8), byte(port))
157
158 if _, err = conn.Write(buf); err != nil {
159 return nil, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
160 }
161
162 if _, err = io.ReadFull(conn, buf[:4]); err != nil {
163 return nil, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
164 }
165
166 failure := "unknown error"
167 if int(buf[1]) < len(socks5Errors) {
168 failure = socks5Errors[buf[1]]
169 }
170
171 if len(failure) > 0 {
172 return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
173 }
174
175 bytesToDiscard := 0
176 switch buf[3] {
177 case socks5IP4:
178 bytesToDiscard = 4
179 case socks5IP6:
180 bytesToDiscard = 16
181 case socks5Domain:
182 _, err := io.ReadFull(conn, buf[:1])
183 if err != nil {
184 return nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
185 }
186 bytesToDiscard = int(buf[0])
187 default:
188 return nil, errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
189 }
190
191 if cap(buf) < bytesToDiscard {
192 buf = make([]byte, bytesToDiscard)
193 } else {
194 buf = buf[:bytesToDiscard]
195 }
196 if _, err = io.ReadFull(conn, buf); err != nil {
197 return nil, errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
198 }
199
200 // Also need to discard the port number
201 if _, err = io.ReadFull(conn, buf[:2]); err != nil {
202 return nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
203 }
204
205 closeConn = nil
206 return conn, nil
207}