def _try_request(self): """ Try to consume a REQUEST message and respond whether we will accept the request. Will setup a TCP relay or an UDP socket to accommodate TCP RELAY and UDP ASSOCIATE requests. After a TCP relay is set up the handler will deactivate itself and change the Connection to a TcpRelayConnection. Further data will be passed on to that handler. :return: False if command could not been processes due to lack of bytes, True otherwise """ self._logger.debug("Client has sent PROXY REQUEST") try: request, offset = socks5_serializer.unpack_serializable( CommandRequest, self.buffer) except PackError: return False self.buffer = self.buffer[offset:] self.state = ConnectionState.PROXY_REQUEST_RECEIVED try: if request.cmd == REQ_CMD_UDP_ASSOCIATE: ensure_future(self.on_udp_associate_request(request)) elif request.cmd == REQ_CMD_BIND: payload = CommandResponse(SOCKS_VERSION, REP_SUCCEEDED, 0, ("127.0.0.1", 1081)) response = socks5_serializer.pack_serializable(payload) self.transport.write(response) self.state = ConnectionState.PROXY_REQUEST_ACCEPTED elif request.cmd == REQ_CMD_CONNECT: self._logger.info("Accepting TCP CONNECT request to %s:%d", *request.destination) self.connect_to = request.destination payload = CommandResponse(SOCKS_VERSION, REP_SUCCEEDED, 0, ("127.0.0.1", 1081)) response = socks5_serializer.pack_serializable(payload) self.transport.write(response) else: self.deny_request(request, "CMD not recognized") except: payload = CommandResponse(SOCKS_VERSION, REP_COMMAND_NOT_SUPPORTED, 0, ("0.0.0.0", 0)) response = socks5_serializer.pack_serializable(payload) self.transport.write(response) self._logger.exception( "Exception thrown, returning unsupported command response") return True
def _try_handshake(self): """ Try to read a HANDSHAKE request :return: False if command could not been processes due to lack of bytes, True otherwise """ try: request, offset = socks5_serializer.unpack_serializable( MethodsRequest, self.buffer) except PackError: # No (complete) HANDSHAKE received, so dont do anything return False # Consume the buffer self.buffer = self.buffer[offset:] # Only accept NO AUTH if request.version != SOCKS_VERSION or 0x00 not in request.methods: self._logger.error("Client has sent INVALID METHOD REQUEST") self.buffer = '' self.close() else: self._logger.info("Client has sent METHOD REQUEST") # Respond that we would like to use NO AUTHENTICATION (0x00) if self.state is not ConnectionState.CONNECTED: response = socks5_serializer.pack_serializable( MethodsResponse(SOCKS_VERSION, 0)) self.transport.write(response) # We are connected now, the next incoming message will be a REQUEST self.state = ConnectionState.CONNECTED return True
def on_incoming_from_tunnel(self, community, circuit, origin, data): """ We received some data from the tunnel community. Dispatch it to the right UDP SOCKS5 socket. """ if circuit.ctype in [ CIRCUIT_TYPE_RP_DOWNLOADER, CIRCUIT_TYPE_RP_SEEDER ]: origin = (community.circuit_id_to_ip(circuit.circuit_id), CIRCUIT_ID_PORT) try: connection = self.cid_to_con[circuit.circuit_id] except KeyError: session_hops = circuit.goal_hops if circuit.ctype != CIRCUIT_TYPE_RP_DOWNLOADER else circuit.goal_hops - 1 if session_hops > len( self.socks_servers) or not self.socks_servers[ session_hops - 1].sessions: self._logger.error("No connection found for %d hops", session_hops) return False connection = next( (s for s in self.socks_servers[session_hops - 1].sessions if s.udp_connection and s.udp_connection.remote_udp_address), None) if connection is None or connection.udp_connection is None: self._logger.error( "Connection has closed or has not gotten an UDP associate") self.connection_dead(connection) return False packet = socks5_serializer.pack_serializable( UdpPacket(0, 0, origin, data)) connection.udp_connection.send_datagram(packet) return True
async def _login(self): self.transport, _ = await get_event_loop().create_connection( lambda: self, *self.proxy_addr) request = MethodsRequest(SOCKS_VERSION, [SOCKS_AUTH_ANON]) data = await self._send(socks5_serializer.pack_serializable(request)) response, _ = socks5_serializer.unpack_serializable( MethodsResponse, data) if response.version != SOCKS_VERSION or response.method != SOCKS_AUTH_ANON: raise Socks5Error('Unsupported proxy server')
def test_encode_decode_udp_packet(): rsv = 0 frag = 0 address = DomainAddress('tracker1.good-tracker.com', 8084) data = b'0x000' encoded = socks5_serializer.pack_serializable( UdpPacket(rsv, frag, address, data)) decoded, _ = socks5_serializer.unpack_serializable(UdpPacket, encoded) assert rsv == decoded.rsv assert frag == decoded.frag assert address == decoded.destination address = DomainAddress('tracker1.unicode-tracker\xc4\xe95\x11$\x00', 8084) encoded = socks5_serializer.pack_serializable( UdpPacket(rsv, frag, address, data)) decoded, _ = socks5_serializer.unpack_serializable(UdpPacket, encoded) assert rsv == decoded.rsv assert frag == decoded.frag assert address == decoded.destination
def deny_request(self, request, reason): """ Deny SOCKS5 request @param Request request: the request to deny """ self.state = ConnectionState.CONNECTED payload = CommandResponse(SOCKS_VERSION, REP_COMMAND_NOT_SUPPORTED, 0, ("0.0.0.0", 0)) response = socks5_serializer.pack_serializable(payload) self.transport.write(response) self._logger.error(f"DENYING SOCKS5 request, reason: {reason}")
def test_encode_decode_command_request(): rsv = 0 address = DomainAddress('tracker1.good-tracker.com', 8084) rep = 0 version = 5 encoded = socks5_serializer.pack_serializable( CommandRequest(version, rep, rsv, address)) decoded, _ = socks5_serializer.unpack_serializable(CommandRequest, encoded) assert version == decoded.version assert rsv == decoded.rsv assert address == decoded.destination address = DomainAddress('tracker1.unicode-tracker\xc4\xe95\x11$\x00', 8084) encoded = socks5_serializer.pack_serializable( CommandResponse(version, rep, rsv, address)) decoded, _ = socks5_serializer.unpack_serializable(CommandResponse, encoded) assert version == decoded.version assert rsv == decoded.rsv assert address == decoded.bind
async def on_udp_associate_request(self, request): # The DST.ADDR and DST.PORT fields contain the address and port that the client expects # to use to send UDP datagrams on for the association. The server MAY use this information # to limit access to the association. self.udp_connection = SocksUDPConnection(self, request.destination) await self.udp_connection.open() ip, _ = self.transport.get_extra_info('sockname') port = self.udp_connection.get_listen_port() self._logger.info( "Accepting UDP ASSOCIATE request to %s:%d (BIND addr %s:%d)", ip, port, *request.destination) payload = CommandResponse(SOCKS_VERSION, REP_SUCCEEDED, 0, (ip, port)) response = socks5_serializer.pack_serializable(payload) self.transport.write(response)
async def _connect_tcp(self, target_addr): try: socket.inet_aton(target_addr[0]) except (ValueError, OSError): target_addr = DomainAddress(*target_addr) request = CommandRequest(SOCKS_VERSION, REQ_CMD_CONNECT, 0, target_addr) data = await self._send(socks5_serializer.pack_serializable(request)) response, _ = socks5_serializer.unpack_serializable( CommandResponse, data) if response.version != SOCKS_VERSION: raise Socks5Error('Unsupported proxy server') if response.reply > 0: raise Socks5Error('TCP connect failed') self.connected_to = target_addr
async def _associate_udp(self, local_addr=None): local_addr = local_addr or ('127.0.0.1', 0) connection = Socks5ClientUDPConnection(self.callback) transport, _ = await get_event_loop().create_datagram_endpoint( lambda: connection, local_addr=local_addr) sock = transport.get_extra_info("socket") request = CommandRequest(SOCKS_VERSION, REQ_CMD_UDP_ASSOCIATE, 0, sock.getsockname()) data = await self._send(socks5_serializer.pack_serializable(request)) response, _ = socks5_serializer.unpack_serializable( CommandResponse, data) connection.proxy_udp_addr = response.bind if response.version != SOCKS_VERSION: raise Socks5Error('Unsupported proxy server') if response.reply > 0: raise Socks5Error('UDP associate failed') self.connection = connection
async def test_socks5_sendto_success(socks5_server): """ Test if sending/receiving a UDP packet works correctly. """ await socks5_server.start() data = b'\x00' target = ('127.0.0.1', 123) client = Socks5Client(('127.0.0.1', socks5_server.port), Mock()) await client.associate_udp() client.sendto(data, target) await sleep(0.1) socks5_server.output_stream.on_socks5_udp_data.assert_called_once() connection = socks5_server.output_stream.on_socks5_udp_data.call_args[0][0] request = socks5_server.output_stream.on_socks5_udp_data.call_args[0][1] assert request.data == data assert request.destination == target packet = socks5_serializer.pack_serializable(UdpPacket(0, 0, target, data)) client.callback.assert_not_called() connection.send_datagram(packet) await sleep(0.1) client.callback.assert_called_once_with(data, target)
def sendto(self, data, target_addr): packet = socks5_serializer.pack_serializable( UdpPacket(0, 0, target_addr, data)) self.transport.sendto(packet, self.proxy_udp_addr)