def test_unpack_for_new_link(self): expected_certs = ( (CertType.LINK, 1, b'0\x82\x02F0\x82\x01\xaf'), (CertType.IDENTITY, 2, b'0\x82\x01\xc90\x82\x012'), (CertType.UNKNOWN, 4, b'\x01\x04\x00\x06m\x1f'), (CertType.UNKNOWN, 5, b'\x01\x05\x00\x06m\n\x01'), (CertType.UNKNOWN, 7, b'\x1a\xa5\xb3\xbd\x88\xb1C'), ) content = test_data('new_link_cells') version_cell, content = Cell.pop(content, 2) self.assertEqual(VersionsCell([3, 4, 5]), version_cell) certs_cell, content = Cell.pop(content, 2) self.assertEqual(CertsCell, type(certs_cell)) self.assertEqual(len(expected_certs), len(certs_cell.certificates)) for i, (cert_type, cert_type_int, cert_prefix) in enumerate(expected_certs): self.assertEqual(cert_type, certs_cell.certificates[i].type) self.assertEqual(cert_type_int, certs_cell.certificates[i].type_int) self.assertTrue(certs_cell.certificates[i].value.startswith(cert_prefix)) auth_challenge_cell, content = Cell.pop(content, 2) self.assertEqual(AuthChallengeCell([1, 3], b'\x89Y\t\x99\xb2\x1e\xd9*V\xb6\x1bn\n\x05\xd8/\xe3QH\x85\x13Z\x17\xfc\x1c\x00{\xa9\xae\x83^K'), auth_challenge_cell) netinfo_cell, content = Cell.pop(content, 2) self.assertEqual(NetinfoCell, type(netinfo_cell)) self.assertEqual(datetime.datetime(2018, 1, 14, 1, 46, 56), netinfo_cell.timestamp) self.assertEqual(Address('127.0.0.1'), netinfo_cell.receiver_address) self.assertEqual([Address('97.113.15.2')], netinfo_cell.sender_addresses) self.assertEqual(ZERO * 492, netinfo_cell.unused) self.assertEqual(b'', content) # check that we've consumed all of the bytes
def test_constructor(self): test_data = ( ((4, b'\x7f\x00\x00\x01'), ExpectedAddress(AddrType.IPv4, 4, '127.0.0.1', b'\x7f\x00\x00\x01')), ((4, b'aq\x0f\x02'), ExpectedAddress(AddrType.IPv4, 4, '97.113.15.2', b'aq\x0f\x02')), ((6, b' \x01\r\xb8\x00\x00\x00\x00\x00\x00\xff\x00\x00B\x83)'), ExpectedAddress( AddrType.IPv6, 6, '2001:0db8:0000:0000:0000:ff00:0042:8329', b' \x01\r\xb8\x00\x00\x00\x00\x00\x00\xff\x00\x00B\x83)')), ((AddrType.IPv4, '127.0.0.1'), ExpectedAddress(AddrType.IPv4, 4, '127.0.0.1', b'\x7f\x00\x00\x01')), ((AddrType.IPv4, '97.113.15.2'), ExpectedAddress(AddrType.IPv4, 4, '97.113.15.2', b'aq\x0f\x02')), ((AddrType.IPv6, '2001:0db8:0000:0000:0000:ff00:0042:8329'), ExpectedAddress( AddrType.IPv6, 6, '2001:0db8:0000:0000:0000:ff00:0042:8329', b' \x01\r\xb8\x00\x00\x00\x00\x00\x00\xff\x00\x00B\x83)')), ((AddrType.IPv6, '2001:0DB8:AC10:FE01::'), ExpectedAddress( AddrType.IPv6, 6, '2001:0db8:ac10:fe01:0000:0000:0000:0000', b' \x01\r\xb8\xac\x10\xfe\x01\x00\x00\x00\x00\x00\x00\x00\x00' )), # collaped and different case ) for (addr_type, addr_value), expected in test_data: addr = Address(addr_value, addr_type) self.assertEqual(expected.type, addr.type) self.assertEqual(expected.type_int, addr.type_int) self.assertEqual(expected.value, addr.value) self.assertEqual(expected.value_bin, addr.value_bin) # when an IPv4 or IPv6 address the type is optional self.assertEqual(AddrType.IPv4, Address('127.0.0.1').type) self.assertEqual(AddrType.IPv6, Address('2001:0DB8:AC10:FE01::').type) self.assertRaisesRegexp( ValueError, re.escape( "Packed IPv4 addresses should be four bytes, but was: '\\x7f\\x00'" ), Address, '\x7f\x00', 4) self.assertRaisesRegexp( ValueError, re.escape( "Packed IPv6 addresses should be sixteen bytes, but was: '\\x7f\\x00'" ), Address, '\x7f\x00', 6) self.assertRaisesRegexp( ValueError, re.escape("'nope' isn't an IPv4 or IPv6 address"), Address, 'nope')
def test_packing(self): test_data = { b'\x04\x04\x7f\x00\x00\x01': Address('127.0.0.1'), b'\x06\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\xff\x00\x00B\x83)': Address('2001:0db8:0000:0000:0000:ff00:0042:8329'), } for cell_bytes, address in test_data.items(): self.assertEqual(cell_bytes, address.pack()) self.assertEqual(address, Address.unpack(cell_bytes)) addr, content = Address.pop( b'\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02\x00\x00\x00\x00') self.assertEqual(b'\x01\x04\x04aq\x0f\x02\x00\x00\x00\x00', content) self.assertEqual(AddrType.IPv4, addr.type) self.assertEqual(4, addr.type_int) self.assertEqual('127.0.0.1', addr.value) self.assertEqual(b'\x7f\x00\x00\x01', addr.value_bin)
b'\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', b'\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00', b'\x01' + ZERO * 468, 5), } VERSIONS_CELLS = { b'\x00\x00\x07\x00\x00': ([], 2), b'\x00\x00\x07\x00\x02\x00\x01': ([1], 2), b'\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03': ([1, 2, 3], 2), b'\x00\x00\x00\x00\x07\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04': ([1, 2, 3, 4], 4), } NETINFO_CELLS = { b'\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + ZERO * (FIXED_PAYLOAD_LEN - 17): (datetime.datetime(2018, 1, 14, 1, 46, 56), Address('127.0.0.1'), [Address('97.113.15.2')], ZERO * 492, 2), b'\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + b'\x01' + ZERO * (FIXED_PAYLOAD_LEN - 18): (datetime.datetime(2018, 1, 14, 1, 46, 56), Address('127.0.0.1'), [Address('97.113.15.2')], b'\x01' + ZERO * 491, 2), } VPADDING_CELL_EMPTY_PACKED = b'\x00\x00\x80\x00\x00' VPADDING_CELLS = { VPADDING_CELL_EMPTY_PACKED: (b'', 2), b'\x00\x00\x80\x00\x01\x08': (b'\x08', 2), b'\x00\x00\x80\x00\x02\x08\x11': (b'\x08\x11', 2), b'\x00\x00\x80\x01\xfd' + RANDOM_PAYLOAD: (RANDOM_PAYLOAD, 2), }
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)
def test_unknown_type(self): addr = Address('hello', 12) self.assertEqual(AddrType.UNKNOWN, addr.type) self.assertEqual(12, addr.type_int) self.assertEqual(None, addr.value) self.assertEqual('hello', addr.value_bin)
CREATED_FAST_CELLS = { (b'\x80\x00\x00\x00\x06\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\xb2' + ZERO * 469): (2147483648, b'\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', b'\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\xb2', ZERO * 469, 5), (b'\x80\x00\x00\x00\x06\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00' + ZERO * 469): (2147483648, b'\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', b'\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00', ZERO * 469, 5), (b'\x80\x00\x00\x00\x06\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00' + b'\x01' + ZERO * 468): (2147483648, b'\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', b'\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00', b'\x01' + ZERO * 468, 5), } VERSIONS_CELLS = { b'\x00\x00\x07\x00\x00': ([], 2), b'\x00\x00\x07\x00\x02\x00\x01': ([1], 2), b'\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03': ([1, 2, 3], 2), b'\x00\x00\x00\x00\x07\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04': ([1, 2, 3, 4], 4), } NETINFO_CELLS = { b'\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + ZERO * (FIXED_PAYLOAD_LEN - 17): (datetime.datetime(2018, 1, 14, 1, 46, 56), Address('127.0.0.1'), [Address('97.113.15.2')], ZERO * 492, 2), b'\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + b'\x01' + ZERO * (FIXED_PAYLOAD_LEN - 18): (datetime.datetime(2018, 1, 14, 1, 46, 56), Address('127.0.0.1'), [Address('97.113.15.2')], b'\x01' + ZERO * 491, 2), } VPADDING_CELL_EMPTY_PACKED = b'\x00\x00\x80\x00\x00' VPADDING_CELLS = { VPADDING_CELL_EMPTY_PACKED: (b'', 2), b'\x00\x00\x80\x00\x01\x08': (b'\x08', 2), b'\x00\x00\x80\x00\x02\x08\x11': (b'\x08\x11', 2), b'\x00\x00\x80\x01\xfd' + RANDOM_PAYLOAD: (RANDOM_PAYLOAD, 2), } CERTS_CELLS = { b'\x00\x00\x81\x00\x01\x00': ([], b'', 2), b'\x00\x00\x81\x00\x04\x01\x01\x00\x00': ([Certificate(1, b'')], b'', 2),
b'\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', b'\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\x00'), } VERSIONS_CELLS = { b'\x00\x00\x07\x00\x00': ([], 2), b'\x00\x00\x07\x00\x02\x00\x01': ([1], 2), b'\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03': ([1, 2, 3], 2), b'\x00\x00\x00\x00\x07\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04': ([1, 2, 3, 4], 4), } NETINFO_CELLS = { b'\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + ZERO * (FIXED_PAYLOAD_LEN - 17): (datetime.datetime(2018, 1, 14, 1, 46, 56), Address('127.0.0.1'), [Address('97.113.15.2')]), } VPADDING_CELL_EMPTY_PACKED = b'\x00\x00\x80\x00\x00' VPADDING_CELLS = { VPADDING_CELL_EMPTY_PACKED: b'', b'\x00\x00\x80\x00\x01\x08': b'\x08', b'\x00\x00\x80\x00\x02\x08\x11': b'\x08\x11', b'\x00\x00\x80\x01\xfd' + RANDOM_PAYLOAD: RANDOM_PAYLOAD, } CERTS_CELLS = { b'\x00\x00\x81\x00\x01\x00': [], b'\x00\x00\x81\x00\x04\x01\x01\x00\x00': [Certificate(1, b'')], b'\x00\x00\x81\x00\x05\x01\x01\x00\x01\x08': [Certificate(1, b'\x08')],