async def connect(self, host, port): self.state = self.CMD loop = asyncio.get_event_loop() bind_addr = ('255.255.255.255', 0) try: logger.info('connecting {}:{}'.format(host, port)) fut = loop.create_connection(Client, host, port) transport, client = await \ asyncio.wait_for(fut, timeout=config.timeout) except (asyncio.TimeoutError, ConnectionRefusedError, socket.gaierror) as e: logger.warn('connect {}'.format(e)) socks_err = socks.Message(socks.VER.SOCKS5, socks.REP.NETWORK_UNREACHABLE, socks.ATYPE.IPV4, bind_addr) rep = protocol.Reply(self.remote, self.user, socks_err) self.tunnel_transport.write(rep.to_packet(self.fuzz)) self.state = self.IDLE return client.channel = self self.remote_transport = transport self.remote = transport._sock_fd bind_addr = transport.get_extra_info('sockname') socks_ok = socks.Message(socks.VER.SOCKS5, socks.REP.SUCCEEDED, socks.ATYPE.IPV4, bind_addr) rep = protocol.Reply(self.remote, self.user, socks_ok) self.tunnel_transport.write(rep.to_packet(self.fuzz)) self.state = self.DATA logger.debug('channel {} opened'.format(self))
async def _handle_client(self, client_reader, client_writer): # ignore client greeting data = await client_reader.read(32) logger.debug('Ignore client greeting ({} bytes)'.format(len(data))) server_greeting = socks.ServerGreeting() client_writer.write(server_greeting.to_bytes()) # recv CMD msg = await socks.Message.from_reader(client_reader) if msg.code is not socks.CMD.CONNECT: logger.warn('unhandle msg {}'.format(msg)) return fut = asyncio.open_connection(msg.addr[0], msg.addr[1]) try: remote_reader, remote_writer = await asyncio.wait_for(fut, 3) except (asyncio.TimeoutError, ConnectionRefusedError): logger.warn('connet {}:{} failed'.format(msg.addr[0], msg.addr[1])) err_reply = socks.Message(ver=socks.VER.SOCKS5, code=socks.REP.CONNECTION_REFUSED, atype=socks.ATYPE.IPV4, addr=('127.0.0.1', 9999)) client_writer.write(err_reply.to_bytes()) return logger.info('connected to {}:{}'.format(msg.addr[0], msg.addr[1])) # send REP bind_address = remote_writer.transport._extra['sockname'] reply = socks.Message(ver=socks.VER.SOCKS5, code=socks.REP.SUCCEEDED, atype=socks.ATYPE.IPV4, addr=bind_address) client_writer.write(reply.to_bytes()) # piping await asyncio.gather(pipe('remote', remote_reader, client_writer), pipe('user', client_reader, remote_writer))
async def start_tunnel(self, loop, host, port): logger.info('negotiate with server {}:{}'.format( config.server_host, config.server_port)) reader, writer = await asyncio.open_connection(host, port) # > Hello hello_request = protocol.Hello() await self.safe_write(writer, hello_request.to_packet(self.cipher)) # < Hello hello_response = await protocol.async_read_packet(reader, self.cipher) logger.debug(hello_response) # > HandShake shake_request = protocol.HandShake(timestamp=hello_response.timestamp) await self.safe_write(writer, shake_request.to_packet(self.cipher)) # < HandShake shake_response = await protocol.async_read_packet(reader, self.cipher) logger.debug(shake_response) logger.info('negotiate done, using fuzz: {}'.format( shake_response.fuzz)) self.fuzz = shake_response.fuzz self.tunnel_reader = reader self.tunnel_writer = writer self.tunnel_task = asyncio.Task(self._handle_tunnel(reader, writer)) def tunnel_done(task): logger.warn('tunnel is closed') sys.exit(2) self.tunnel_task.add_done_callback(tunnel_done) return True
def data_received(self, data): length = len(data) if self.remains > 0: # remaining part of previous packet if length == self.remains: self.buf.extend(data) self.packet_received(self.buf) self.remains = 0 self.buf.clear() elif length < self.remains: self.buf.extend(data) self.remains -= length elif length > self.remains: self.buf.extend(data[:self.remains]) self.packet_received(self.buf) self.remains = 0 self.buf.clear() self.data_received(data[self.remains:]) return # start of packet assert length >= 6, 'packet length too small' need_len, = struct.unpack('!I', data[2:6]) real_len = length - 6 self.remains = need_len - real_len if self.remains == 0: self.packet_received(data) elif self.remains > 0: self.buf.extend(data) elif self.remains < 0: logger.debug('more than one packet recevied at once') self.packet_received(data[:6 + need_len]) remaining = data[6 + need_len:] self.remains = 0 self.data_received(remaining)
async def _handle_user(self, user): # ignore client SOCKS5 greeting data = await user.reader.read(256) logger.debug('ignore SOCK5 greeting ({} bytes)'.format(len(data))) # response greeting without auth server_greeting = socks.ServerGreeting() await self.safe_write(user.writer, server_greeting.to_bytes()) # recv CMD try: msg = await socks.Message.from_reader(user.reader) except asyncio.streams.IncompleteReadError: self._delete_user(user) return if msg.code is not socks.CMD.CONNECT: logger.warn('unhandle msg {}'.format(msg)) rep = socks.Message(socks.VER.SOCKS5, socks.REP.COMMAND_NOT_SUPPORTED, socks.ATYPE.IPV4, ('0', 0)) await self.safe_write(user.writer, rep.to_bytes()) return logger.info('connecting {}:{}'.format(msg.addr[0], msg.addr[1])) # send to tunnel connect_reqeust = protocol.Request(user.user_id, 0, msg) await self.safe_write(self.tunnel_writer, connect_reqeust.to_packet(self.fuzz)) await self._pipe_user(user)
def connection_lost(self, exc): global concurrent concurrent -= 1 logger.debug('connection to user lost') self.state = self.CLOSED if self.client_transport is not None: logger.debug('abort client') self.client_transport.abort()
def close(self): if self.state == self.IDLE: return self.state = self.IDLE if self.remote_transport is not None: self.remote_transport.abort() packet = protocol.Close(self.user) self.tunnel_transport.write(packet.to_packet(self.fuzz)) logger.debug('channel {} closed'.format(self))
def connection_made(self, transport): global concurrent concurrent += 1 logger.debug('{} connected'.format( transport.get_extra_info('peername'))) self.transport = transport self.client_transport = None self.user = transport.get_extra_info('peername') self.state = self.INIT
def connection_made(self, transport): logger.debug('client {}:{} connected'.format( *transport.get_extra_info('peername'))) self.transport = transport self.tunnel = None self.state = self.GREETING self.buf = bytearray() self.remains = 0 self.cipher = cryption.AES256CBC(config.password) self.fuzz = None
def _accept_user(self, user_reader, user_writer): logger.debug('user accepted') user = User(user_reader, user_writer) task = asyncio.Task(self._handle_user(user)) user.task = task self.users[user.user_id] = user def user_done(task): logger.debug('user task done') task.add_done_callback(user_done)
def stop(self, loop): """ Stops the TCP server, i.e. closes the listening socket(s). This method runs the loop until the server sockets are closed. """ if self.server is not None: logger.debug('canceling tasks') for task in self.clients: task.cancel() loop.run_until_complete(asyncio.wait([t for t in self.clients])) self.server.close() loop.run_until_complete(self.server.wait_closed()) self.server = None
async def pipe(name, reader, writer): while True: try: data = await reader.read(2048) except ConnectionResetError as e: logger.warn('connection reset by ' + name) break except asyncio.CancelledError as e: logger.debug('pipe canceled.') break if len(data) == 0: logger.debug('{} connection closed'.format(name)) break writer.write(data) return None
def _accept_client(self, client_reader, client_writer): """ This method accepts a new client connection and creates a Task to handle this client. self.clients is updated to keep track of the new client. """ logger.debug('=== begin ===') # start a new Task to handle this specific client connection task = asyncio.Task(self._handle_client(client_reader, client_writer)) self.clients[task] = (client_reader, client_writer) def client_done(task): logger.debug('=== end ===') del self.clients[task] task.add_done_callback(client_done)
async def _handle_tunnel(self, reader, writer): logger.debug('_handle_tunnel started') while True: packet = await protocol.async_read_packet(reader, self.fuzz) if packet.mtype is protocol.MTYPE.REPLY: # received a SOCKS reply, update mapping # and forward to corresponding user remote_id = packet.src user_id = packet.dst user = self._get_user(user_id) if user is None: # Tell server to close continue await self.safe_write(user.writer, packet.msg.to_bytes()) user.remote_id = remote_id elif packet.mtype is protocol.MTYPE.RELAYING: # received raw data, forwarding remote_id = packet.src user_id = packet.dst user = self._get_user(user_id) if user is None: # Tell server to close continue await self.safe_write(user.writer, packet.payload) elif packet.mtype is protocol.MTYPE.CLOSE: # close user tansport user_id = packet.src logger.debug( 'remote disconnected, close user {}'.format(user_id)) user = self._get_user(user_id) if user is None: # ignore continue self._delete_user(user) else: logger.warn('unknown packet {}'.format(packet)) logger.debug('_handle_tunnel exited')
def connection_lost(self, exc): self.state = self.CLOSING logger.debug('client {}:{} disconnected'.format( *self.transport.get_extra_info('peername'))) if self.tunnel is not None: self.tunnel.close()
def _user_closed(self, user): logger.debug('{} closed'.format(user)) if user.established: self.tunnel_writer.write( protocol.Close(user.user_id).to_packet(self.fuzz)) user.writer.transport.abort()
def user_done(task): logger.debug('user task done')
def client_done(task): logger.debug('=== end ===') del self.clients[task]
def connection_lost(self, exc): logger.debug('connection to server lost') self.state = self.CLOSED if self.server_transport is not None: logger.debug('abort server') self.server_transport.abort()