This repository has been archived by the owner on Jul 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
client.py
140 lines (119 loc) · 4.1 KB
/
client.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
import asyncio
import functools
import logging
import socket
import ssl
import struct
from common import (
ClientConfig, get_logging_config, handle_tcp, parse_commands,
write_and_drain, clean_tasks, set_tcp_nodelay, BUF_SIZE
)
ADDRTYPE_IPV4 = 1
ADDRTYPE_HOST = 3
logger = logging.getLogger('client')
def parse_header(data):
addrtype = ord(data[0:1])
dest_addr = None
dest_port = None
header_length = 0
if addrtype == ADDRTYPE_IPV4:
if len(data) >= 7:
dest_addr = socket.inet_ntoa(data[1:5])
dest_port = struct.unpack('>H', data[5:7])[0]
header_length = 7
else:
logger.warn('header is too short')
elif addrtype == ADDRTYPE_HOST:
if len(data) > 2:
addrlen = ord(data[1:2])
if len(data) >= 2 + addrlen:
dest_addr = data[2:2 + addrlen]
dest_port = struct.unpack(
'>H', data[2 + addrlen:4 + addrlen]
)[0]
header_length = 4 + addrlen
else:
logger.warn('header is too short')
else:
logger.warn('header is too short')
else:
logger.warn('unsupported addrtype %s' % addrtype)
if dest_addr is None:
return None
elif type(dest_addr) == bytes:
dest_addr = dest_addr.decode('utf-8')
return addrtype, dest_addr, dest_port, header_length
@asyncio.coroutine
def request(conf, ssl_context, reader, writer):
set_tcp_nodelay(writer)
address = writer.get_extra_info('peername')
logger.info('Connected from {}:{}'.format(*address))
data = yield from reader.read(BUF_SIZE)
yield from write_and_drain(writer, b'\x05\x00')
data = yield from reader.read(BUF_SIZE)
cmd = ord(data[1:2])
if cmd != 1:
raise Exception('Command not supported')
result = parse_header(data[3:])
if not result:
raise Exception('Header cannot be parsed')
r_reader, r_writer = yield from asyncio.open_connection(
host=conf.server_ip, port=conf.server_port, ssl=ssl_context
)
set_tcp_nodelay(r_writer)
local = r_writer.get_extra_info('sockname')
reply = b'\x05\x00\x00\x01'
reply += socket.inet_aton(local[0]) + struct.pack('>H', local[1])
yield from write_and_drain(writer, reply)
# socks5 connection opened
dest = '{}:{}'.format(result[1], result[2])
dest_b = dest.encode('utf-8') + b'\n'
logger.info('Connecting to {}'.format(dest))
yield from write_and_drain(r_writer, dest_b)
while True:
result = yield from handle_tcp(reader, writer, r_reader, r_writer)
r_writer.close()
if not result:
writer.close()
return
r_reader, r_writer = yield from asyncio.open_connection(
host=conf.server_ip, port=conf.server_port, ssl=ssl_context
)
set_tcp_nodelay(r_writer)
logger.info('Connecting to {}'.format(dest))
yield from write_and_drain(r_writer, dest_b)
def main():
cmds = parse_commands(logger.name)
conf = ClientConfig(cmds)
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_context.check_hostname = False
ssl_context.load_verify_locations(conf.crt_file)
log_cfg = get_logging_config(
log_level=conf.log_level, log_file=conf.log_file
)
logging.basicConfig(**log_cfg)
logger.debug(cmds)
event_loop = asyncio.get_event_loop()
factory = asyncio.start_server(
functools.partial(request, conf, ssl_context), host=conf.bind_ip,
port=conf.listen_port
)
logger.info('Server address {}:{}'.format(
conf.server_ip, conf.server_port)
)
logger.info('Listening on {}:{}'.format(
conf.bind_ip, conf.listen_port)
)
server = event_loop.run_until_complete(factory)
try:
event_loop.run_forever()
except KeyboardInterrupt:
clean_tasks(event_loop)
finally:
logger.debug('Closing client')
server.close()
event_loop.run_until_complete(server.wait_closed())
logger.debug('Closing event loop')
event_loop.close()
if __name__ == '__main__':
main()