-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSimpleSocks5.py
220 lines (164 loc) · 5.91 KB
/
SimpleSocks5.py
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
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env python3
# requires Python 3.8+
import asyncio
import socket
import struct
__version__ = '1.0.4'
__author__ = 'spcharc'
class IncorrectFormat(Exception):
pass
class SocksVersionIncorrect(Exception):
pass
class AuthMethodNotSupported(Exception):
pass
class UnsupportedCommand(Exception):
pass
class AddressTypeNotSupported(Exception):
pass
class HostNotFound(Exception):
pass
class ConnectionRefused(Exception):
pass
class ConnectionFailed(Exception):
pass
class InternalError(Exception):
pass
async def pipe_data(reader, writer):
# pipes data from reader into writer
while len(data := await reader.read(8192)): # 8kb
writer.write(data)
await writer.drain()
writer.close()
await writer.wait_closed()
async def handler_raises(reader, writer):
async def read_struct(data_format):
length = struct.calcsize(data_format)
content = await reader.readexactly(length)
return struct.unpack(data_format, content)
# https://tools.ietf.org/html/rfc1928
ver, nmethods = await read_struct('!BB')
if ver != 5: # ver should be 5
raise IncorrectFormat
if nmethods == 0:
raise AuthMethodNotSupported
methods = await reader.readexactly(nmethods)
if 0 not in methods: # 'int' in 'bytes'
raise AuthMethodNotSupported
writer.write(struct.pack('!BB', 5, 0)) # NO AUTHENTICATION REQUIRED
await writer.drain()
# -------- negotiation ends --------
ver, cmd, rsv, atyp = await read_struct('!BBBB')
if ver != 5: # ver should be 5
raise SocksVersionIncorrect
if cmd != 1: # 1=connect, 2=bind, 3=udp associate
raise UnsupportedCommand
if rsv != 0: # rsv should be 0
raise IncorrectFormat
if atyp == 1: # ipv4
host = await reader.readexactly(4)
hostname = socket.inet_ntop(socket.AF_INET, host)
elif atyp == 3: # domain
length, = await read_struct('!B')
hostname = (await reader.readexactly(length)).decode('ascii')
elif atyp == 4: # ipv6
host = await reader.readexactly(16)
hostname = socket.inet_ntop(socket.AF_INET6, host)
else:
raise AddressTypeNotSupported
port, = await read_struct('!H')
print('Connect to', hostname, ':', port)
try:
reader2, writer2 = await asyncio.open_connection(hostname, port)
except socket.gaierror:
raise HostNotFound
except ConnectionRefusedError:
raise ConnectionRefused
except Exception:
raise ConnectionFailed
conn_socket = writer2.get_extra_info('socket')
conn_ip, conn_port = conn_socket.getsockname()[0:2]
# in case of AF_INET6, a tuple of length 4 would be returned
if conn_socket.family == socket.AF_INET:
conn_family = 1
elif conn_socket.family == socket.AF_INET6:
conn_family = 4
else:
raise InternalError
writer.write(struct.pack('!BBBB', 5, 0, 0, conn_family))
writer.write(socket.inet_pton(conn_socket.family, conn_ip))
writer.write(struct.pack('!H', conn_port))
await writer.drain()
await asyncio.gather(pipe_data(reader2, writer),
pipe_data(reader, writer2),
return_exceptions=True)
async def handler(reader, writer):
# wrap handler_raises, this function handles exceptions
try:
await handler_raises(reader, writer)
except IncorrectFormat:
writer.close()
await writer.wait_closed()
print('ERROR: Incorrect data format. Using socks5?')
except SocksVersionIncorrect:
writer.close()
await writer.wait_closed()
print('ERROR: Socks version should be 5.')
except asyncio.IncompleteReadError:
writer.close()
await writer.wait_closed()
print('INFO: Peer closed socket unexpectedly.')
except AuthMethodNotSupported:
writer.write(struct.pack('!BB', 5, 255)) # NO ACCEPTABLE METHODS
await writer.drain()
writer.close()
await writer.wait_closed()
print('ERROR: This program only supports socks5 without encryption.')
except UnsupportedCommand:
writer.write(struct.pack('!BBBBIH', 5, 7, 0, 1, 0, 0))
# Command not supported
await writer.drain()
writer.close()
await writer.wait_closed()
print('ERROR: This program only supports socks5 CONNECT command.')
except AddressTypeNotSupported:
writer.write(struct.pack('!BBBBIH', 5, 8, 0, 1, 0, 0))
# Address type not supported
await writer.drain()
writer.close()
await writer.wait_closed()
print('ERROR: This program does not support this address type.')
except HostNotFound:
writer.write(struct.pack('!BBBBIH', 5, 4, 0, 1, 0, 0))
# Host unreachable
await writer.drain()
writer.close()
await writer.wait_closed()
except ConnectionRefused:
writer.write(struct.pack('!BBBBIH', 5, 5, 0, 1, 0, 0))
# Network unreachable
await writer.drain()
writer.close()
await writer.wait_closed()
except ConnectionFailed:
writer.write(struct.pack('!BBBBIH', 5, 3, 0, 1, 0, 0))
# Network unreachable
await writer.drain()
writer.close()
await writer.wait_closed()
except InternalError:
writer.write(struct.pack('!BBBBIH', 5, 1, 0, 1, 0, 0))
# general SOCKS server failure (should not reach here)
await writer.drain()
writer.close()
await writer.wait_closed()
print('ERROR: Socket family incorrect ... this should not happen.')
async def main(addr, port):
await asyncio.start_server(handler, addr, port)
if __name__ == '__main__':
addr = '0.0.0.0'
port = 1080
# define which address and port to listen on
loop = asyncio.new_event_loop()
loop.run_until_complete(main(addr, port))
print('Listening on', addr, ':', port)
loop.run_forever()