def from_socket_file(infile): global GOT_SOCKET if THROW_SOCKET: raise stem.SocketError("Ded") else: GOT_SOCKET = infile return MockController()
def _make_socket_monkey_patch(self): try: control_socket = _orig_socket(socket.AF_INET, socket.SOCK_STREAM) control_socket.connect((self.address, self.port)) return control_socket except socket.error as exc: raise stem.SocketError(exc)
def test_start_persistent_method_catches_socket_error_exception_on_control_from_port_with_args( self, socket, controller): sub = self.x.run_check = MagicMock() controller.from_port.side_effect = stem.SocketError() target = self.x.put_error_to_queue = MagicMock() self.x.start_persistent() self.assertTrue(target.called)
def _make_socket(self): try: if "socket_noproxy" in dir(socket): # Socket proxy-patched, use non-proxy one control_socket = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM) else: control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TODO: repeated code - consider making a separate method control_socket.connect((self._control_addr, self._control_port)) return control_socket except socket.error as exc: raise stem.SocketError(exc)
def from_port(ip, port): if THROW_SOCKET: raise stem.SocketError("Ded") else: return MockController()
async def connect(address: str, port: int, link_protocols: Sequence['stem.client.datatype.LinkProtocol'] = DEFAULT_LINK_PROTOCOLS) -> 'stem.client.Relay': # type: ignore """ Establishes a connection with the given ORPort. :param address: ip address of the relay :param port: ORPort of the relay :param link_protocols: acceptable link protocol versions :raises: * **ValueError** if address or port are invalid * :class:`stem.SocketError` if we're unable to establish a connection """ relay_addr = Address(address) if not stem.util.connection.is_valid_port(port): raise ValueError("'%s' isn't a valid port" % port) elif not link_protocols: raise ValueError("Connection can't be established without a link protocol.") try: conn = stem.socket.RelaySocket(address, port) await conn.connect() except stem.SocketError as exc: if 'Connect call failed' in str(exc): raise stem.SocketError("Failed to connect to %s:%i. Maybe it isn't an ORPort?" % (address, port)) # If not an ORPort (for instance, mistakenly connecting to a ControlPort # instead) we'll likely fail during SSL negotiation. This can result # in a variety of responses so normalizing what we can... # # Debian 9.5: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:661) # Ubuntu 16.04: [SSL: UNKNOWN_PROTOCOL] unknown protocol (_ssl.c:590) # Ubuntu 12.04: [Errno 1] _ssl.c:504: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol if 'unknown protocol' in str(exc) or 'wrong version number' in str(exc): raise stem.SocketError("Failed to SSL authenticate to %s:%i. Maybe it isn't an ORPort?" % (address, port)) raise # To negotiate our link protocol the first VERSIONS cell is expected to use # a circuit ID field size from protocol version 1-3 for backward # compatibility... # # The first VERSIONS cell, and any cells sent before the # first VERSIONS cell, always have CIRCID_LEN == 2 for backward # compatibility. await conn.send(stem.client.cell.VersionsCell(link_protocols).pack(2)) # type: ignore response = await conn.recv() # Link negotiation ends right away if we lack a common protocol # version. (#25139) if not response: await conn.close() raise stem.SocketError('Unable to establish a common link protocol with %s:%i' % (address, port)) versions_reply = stem.client.cell.Cell.pop(response, 2)[0] # type: stem.client.cell.VersionsCell # type: ignore common_protocols = set(link_protocols).intersection(versions_reply.versions) if not common_protocols: await conn.close() raise stem.SocketError('Unable to find a common link protocol. We support %s but %s:%i supports %s.' % (', '.join(map(str, link_protocols)), address, port, ', '.join(map(str, versions_reply.versions)))) # Establishing connections requires sending a NETINFO, but including our # address is optional. We can revisit including it when we have a usecase # where it would help. link_protocol = max(common_protocols) await conn.send(stem.client.cell.NetinfoCell(relay_addr, []).pack(link_protocol)) return Relay(conn, link_protocol)
def connect(address, port, link_protocols = DEFAULT_LINK_PROTOCOLS): """ Establishes a connection with the given ORPort. :param str address: ip address of the relay :param int port: ORPort of the relay :param tuple link_protocols: acceptable link protocol versions :raises: * **ValueError** if address or port are invalid * :class:`stem.SocketError` if we're unable to establish a connection """ relay_addr = Address(address) if not stem.util.connection.is_valid_port(port): raise ValueError("'%s' isn't a valid port" % port) elif not link_protocols: raise ValueError("Connection can't be established without a link protocol.") try: conn = stem.socket.RelaySocket(address, port) except stem.SocketError as exc: if 'Connection refused' in str(exc): raise stem.SocketError("Failed to connect to %s:%i. Maybe it isn't an ORPort?" % (address, port)) elif 'SSL: ' in str(exc): raise stem.SocketError("Failed to SSL authenticate to %s:%i. Maybe it isn't an ORPort?" % (address, port)) else: raise # To negotiate our link protocol the first VERSIONS cell is expected to use # a circuit ID field size from protocol version 1-3 for backward # compatibility... # # The first VERSIONS cell, and any cells sent before the # first VERSIONS cell, always have CIRCID_LEN == 2 for backward # compatibility. conn.send(stem.client.cell.VersionsCell(link_protocols).pack(2)) response = conn.recv() # Link negotiation ends right away if we lack a common protocol # version. (#25139) if not response: conn.close() raise stem.SocketError('Unable to establish a common link protocol with %s:%i' % (address, port)) versions_reply = stem.client.cell.Cell.pop(response, 2)[0] common_protocols = set(link_protocols).intersection(versions_reply.versions) if not common_protocols: conn.close() raise stem.SocketError('Unable to find a common link protocol. We support %s but %s:%i supports %s.' % (', '.join(link_protocols), address, port, ', '.join(versions_reply.versions))) # Establishing connections requires sending a NETINFO, but including our # address is optional. We can revisit including it when we have a usecase # where it would help. link_protocol = max(common_protocols) conn.send(stem.client.cell.NetinfoCell(relay_addr, []).pack(link_protocol)) return Relay(conn, link_protocol)
class TestConnect(unittest.TestCase): @patch('sys.stdout', new_callable=StringIO) @patch('stem.util.system.is_running') @patch('os.path.exists', Mock(return_value=True)) @patch('stem.socket.ControlSocketFile', Mock(side_effect=stem.SocketError('failed'))) @patch('stem.socket.ControlPort', Mock(side_effect=stem.SocketError('failed'))) @patch('stem.connection._connect_auth', Mock()) def test_failue_with_the_default_endpoint(self, is_running_mock, stdout_mock): is_running_mock.return_value = False self._assert_connect_fails_with( {}, stdout_mock, "Unable to connect to tor. Are you sure it's running?") is_running_mock.return_value = True self._assert_connect_fails_with( {}, stdout_mock, "Unable to connect to tor. Maybe it's running without a ControlPort?" ) @patch('sys.stdout', new_callable=StringIO) @patch('os.path.exists') @patch('stem.util.system.is_running', Mock(return_value=True)) @patch('stem.socket.ControlSocketFile', Mock(side_effect=stem.SocketError('failed'))) @patch('stem.socket.ControlPort', Mock(side_effect=stem.SocketError('failed'))) @patch('stem.connection._connect_auth', Mock()) def test_failure_with_a_custom_endpoint(self, path_exists_mock, stdout_mock): path_exists_mock.return_value = True self._assert_connect_fails_with( { 'control_port': ('127.0.0.1', 80), 'control_socket': None }, stdout_mock, "Unable to connect to 127.0.0.1:80: failed") self._assert_connect_fails_with( { 'control_port': None, 'control_socket': '/tmp/my_socket' }, stdout_mock, "Unable to connect to '/tmp/my_socket': failed") path_exists_mock.return_value = False self._assert_connect_fails_with( { 'control_port': ('127.0.0.1', 80), 'control_socket': None }, stdout_mock, "Unable to connect to 127.0.0.1:80: failed") self._assert_connect_fails_with( { 'control_port': None, 'control_socket': '/tmp/my_socket' }, stdout_mock, "The socket file you specified (/tmp/my_socket) doesn't exist") @patch('stem.socket.ControlPort') @patch('os.path.exists', Mock(return_value=False)) @patch('stem.connection._connect_auth', Mock()) def test_getting_a_control_port(self, port_mock): stem.connection.connect() port_mock.assert_called_once_with('127.0.0.1', 9051) port_mock.reset_mock() stem.connection.connect(control_port=('255.0.0.10', 80), control_socket=None) port_mock.assert_called_once_with('255.0.0.10', 80) @patch('stem.socket.ControlSocketFile') @patch('os.path.exists', Mock(return_value=True)) @patch('stem.connection._connect_auth', Mock()) def test_getting_a_control_socket(self, socket_mock): stem.connection.connect() socket_mock.assert_called_once_with('/var/run/tor/control') socket_mock.reset_mock() stem.connection.connect(control_port=None, control_socket='/tmp/my_socket') socket_mock.assert_called_once_with('/tmp/my_socket') def _assert_connect_fails_with(self, args, stdout_mock, msg): result = stem.connection.connect(**args) if result is not None: self.fail() # Python 3.x seems to have an oddity where StringIO has prefixed null # characters (\x00) after we call truncate(). This could be addressed # a couple ways... # # * Don't use a stdout mock more than once. # * Strip the null characters. # # Opting for the second (which is admittedly a hack) so the tests are a # little nicer. stdout_output = stdout_mock.getvalue() stdout_mock.truncate(0) self.assertEqual(msg, stdout_output.strip().lstrip('\x00')) @patch('stem.connection.authenticate') def test_auth_success(self, authenticate_mock): control_socket = Mock() stem.connection._connect_auth(control_socket, None, False, None, None) authenticate_mock.assert_called_with(control_socket, None, None) authenticate_mock.reset_mock() stem.connection._connect_auth(control_socket, 's3krit!!!', False, '/my/chroot', None) authenticate_mock.assert_called_with(control_socket, 's3krit!!!', '/my/chroot') @patch('getpass.getpass') @patch('stem.connection.authenticate') def test_auth_success_with_password_prompt(self, authenticate_mock, getpass_mock): control_socket = Mock() def authenticate_mock_func(controller, password, *args): if password is None: raise stem.connection.MissingPassword('no password') elif password == 'my_password': return None # success else: raise ValueError('Unexpected authenticate_mock input: %s' % password) authenticate_mock.side_effect = authenticate_mock_func getpass_mock.return_value = 'my_password' stem.connection._connect_auth(control_socket, None, True, None, None) authenticate_mock.assert_any_call(control_socket, None, None) authenticate_mock.assert_any_call(control_socket, 'my_password', None) @patch('sys.stdout', new_callable=StringIO) @patch('stem.connection.authenticate') def test_auth_failure(self, authenticate_mock, stdout_mock): control_socket = stem.socket.ControlPort(connect=False) authenticate_mock.side_effect = stem.connection.IncorrectSocketType( 'unable to connect to socket') self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Please check in your torrc that 9051 is the ControlPort.') control_socket = stem.socket.ControlSocketFile(connect=False) self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Are you sure the interface you specified belongs to') authenticate_mock.side_effect = stem.connection.UnrecognizedAuthMethods( 'unable to connect', ['telepathy']) self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Tor is using a type of authentication we do not recognize...\n\n telepathy' ) authenticate_mock.side_effect = stem.connection.IncorrectPassword( 'password rejected') self._assert_authenticate_fails_with(control_socket, stdout_mock, 'Incorrect password') authenticate_mock.side_effect = stem.connection.UnreadableCookieFile( 'permission denied', '/tmp/my_cookie', False) self._assert_authenticate_fails_with( control_socket, stdout_mock, "We were unable to read tor's authentication cookie...\n\n Path: /tmp/my_cookie\n Issue: permission denied" ) authenticate_mock.side_effect = stem.connection.OpenAuthRejected( 'crazy failure') self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Unable to authenticate: crazy failure') def _assert_authenticate_fails_with(self, control_socket, stdout_mock, msg): result = stem.connection._connect_auth(control_socket, None, False, None, None) if result is not None: self.fail() # _connect_auth() was successful stdout_output = stdout_mock.getvalue() stdout_mock.truncate(0) if msg not in stdout_output: self.fail( "Expected...\n\n%s\n\n... which couldn't be found in...\n\n%s" % (msg, stdout_output))