-
Notifications
You must be signed in to change notification settings - Fork 4
/
socks5.go
209 lines (176 loc) · 4.63 KB
/
socks5.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
200
201
202
203
204
205
206
207
208
209
package main
// This file contains the SOCKS5 implementation of the proxy interface defined in proxy.go
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strconv"
)
type socks5 struct {
baseProxy
}
// address returns the address where the SOCKS5 proxy is exposed, i.e. proxy.host:proxy.port
func (p socks5) address() string {
return fmt.Sprintf("%s:%s", p.host, p.port)
}
// handshake takes net.Conn (representing a TCP socket) and an address and returns the same net.Conn connected to the provided address through the SOCKS5 proxy
func (p socks5) handshake(conn net.Conn, address string) (err error) {
gMetaLogger.Debugf("Entering SOCKS5 handshake(%v, %v)", conn, address)
defer func() { gMetaLogger.Debugf("Exiting SOCKS5 handshake(%v, %v)", conn, address) }()
//Implements SOCKS5 proxy handshake
if conn == nil {
err = fmt.Errorf("nil conn was provided")
return
}
reader := bufio.NewReader(conn)
if p.user != "" {
//Means that user/password authentication method (0x02) is supported
_, err = conn.Write([]byte{5, 2, 0, 2})
} else {
//Means only "no authentication" is supported
_, err = conn.Write([]byte{5, 1, 0})
}
if err != nil {
return
}
//Read server response containing |VER|METHOD|
buff := make([]byte, 2)
_, err = io.ReadFull(reader, buff)
if err != nil {
return
}
ver := buff[0]
method := buff[1]
if ver != byte(5) {
err = fmt.Errorf("SOCKS5 server's version != 5")
return
}
switch method {
case 0:
case 2:
err = fmt.Errorf("user/password method not yet implemented")
return
default:
err = fmt.Errorf("unsupported authentication mechanism")
return
}
buff = make([]byte, 4, 260)
buff[0] = byte(5)
buff[1] = byte(1)
buff[2] = byte(0)
addrBytes, atyp, err := stringToAddr(address)
if err != nil {
return
}
buff[3] = atyp
buff = append(buff, addrBytes...)
_, err = conn.Write(buff)
if err != nil {
return
}
gMetaLogger.Debugf("sent the following SOCKS request: %v", buff)
buff = make([]byte, 4)
_, err = io.ReadFull(reader, buff)
if err != nil {
err = fmt.Errorf("error reading SOCKS response: %w", err)
return
}
gMetaLogger.Debugf("received the following SOCKS response: %v", buff)
if rep := buff[1]; rep != byte(0) {
switch rep {
case 0x01:
err = fmt.Errorf("general SOCKS server failure")
case 0x02:
err = fmt.Errorf("connection not allowed by ruleset")
case 0x03:
err = fmt.Errorf("network unreachable")
case 0x04:
err = fmt.Errorf("host unreachable")
case 0x05:
err = fmt.Errorf("connection refused")
case 0x06:
err = fmt.Errorf("TTL expired")
case 0x07:
err = fmt.Errorf("command not supported")
case 0x08:
err = fmt.Errorf("address type not supported")
default:
err = fmt.Errorf("custom SOCKS5 error byte %v", rep)
}
return
}
err = nil
return
}
// stringToAddr takes a address string (format host:port) and returns the SOCKS5 defined address type atyp and the address bytes data in the SOCKS5 format (see RFC 1928)
func stringToAddr(addr string) (data []byte, atyp byte, err error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return
}
hostBytes := net.ParseIP(host)
if hostBytes == nil { // host is a domain name
atyp = 3
hostBytes = []byte(host)
hostBytes = append([]byte{byte(len(hostBytes))}, hostBytes...)
} else { // host is an IP address
hostBytesV4 := hostBytes.To4()
if hostBytesV4 != nil { // host is an IPv4 address
atyp = 1
hostBytes = hostBytesV4
} else { // host is an IPv6 address
atyp = 4
}
}
portInt, err := strconv.Atoi(port)
if err != nil {
return
}
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, uint16(portInt))
data = append(hostBytes, portBytes...)
return
}
// addrToString takes a reader pointing to a SOCKS5 address formatted buffer and a SOCKS5 address type atyp (see RFC 1928) and returns an address string addr (format host:port)
func addrToString(reader io.Reader, atyp byte) (addr string, err error) {
var buf []byte
switch atyp {
case atypIPV4:
buf = make([]byte, 4)
case atypIPV6:
buf = make([]byte, 16)
case atypDomain:
size := make([]byte, 1)
_, err = reader.Read(size)
if err != nil {
return
}
buf = make([]byte, int(size[0]))
default:
err = errors.New("invalid atyp value")
return
}
// Read destination address
_, err = io.ReadFull(reader, buf)
if err != nil {
return
}
var host string
if atyp != atypDomain {
host = net.IP(buf).String()
} else {
host = string(buf)
}
// Read destination port
buf = make([]byte, 2)
_, err = io.ReadFull(reader, buf)
if err != nil {
return
}
port := int(binary.BigEndian.Uint16(buf))
addr = net.JoinHostPort(host, strconv.Itoa(port))
return
}