01. Connection

There are several ways to connect to Tor’s control port, depending on the daemon configuration. Tor configuration is out of this scope, to find out how to configure the control port, please take a look at the torrc manpage and specifically the ControlPort option.

This service typically listens on port TCP/9051 or on a local UNIX socket (on Linux).

TCP port connection

The following code shows how to connect to the control port through the TCP local port:

examples/connect_from_port.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4import os
 5from aiostem import Controller
 6
 7async def main():
 8    host = os.environ.get('AIOSTEM_HOST', 'localhost')
 9    port = os.environ.get('AIOSTEM_PORT', 9051)
10
11    print(f'[>] Connecting to {host} on port {port}')
12    async with Controller.from_port(host, int(port)) as ctrl:
13        reply = await ctrl.protocol_info()
14        reply.raise_for_status()
15        print(f'[+] Connected to Tor v{reply.data.tor_version}')
16
17if __name__ == '__main__':
18    asyncio.run(main())

This code uses Controller.from_port(), which is a helper method to create a new client (a controller) from pair of host and port.

This is what the output of this script looks like:

$ python examples/connect_from_port.py
[>] Connecting to localhost on port 9051
[+] Connected to Tor v0.4.8.13

Socket file connection

The following code is an alternative version connecting through a local socket file:

examples/connect_from_socket.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4import os
 5from aiostem import Controller
 6
 7async def main():
 8    path = os.environ.get('AIOSTEM_PATH', '/run/tor/control')
 9    print(f'[>] Connecting to {path}')
10    async with Controller.from_path(path) as ctrl:
11        reply = await ctrl.protocol_info()
12        reply.raise_for_status()
13        print(f'[+] Connected to Tor v{reply.data.tor_version}')
14
15if __name__ == '__main__':
16    asyncio.run(main())

This code uses Controller.from_path(), which is another helper method used to create a controller for a socket file. This code also uses Controller.protocol_info() which is one of the rare commands you can run while not authenticated. This is intended here to get the version of the remote Tor daemon.

This is what the output of this script looks like:

$ python examples/connect_from_socket.py
[>] Connecting to /run/tor/control
[+] Connected to Tor v0.4.8.13

When available, you should probably prefer this version since local sockets have less overhead over a full TCP connection.

Advanced connection

Controller.from_port() and Controller.from_path() are only wrappers using classes derived from ControlConnector. Users can build new sub-classes for custom uses, such as providing support for TLS or any kind of proxy. A Controller only requires the connector to provide connect() which returns a tuple of asyncio.StreamReader and asyncio.StreamWriter.

The following code connects to the TCP port using ControlConnectorPort:

examples/connect_with_connector.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4import os
 5from aiostem.connector import ControlConnectorPort
 6from aiostem import Controller
 7
 8async def main():
 9    host = os.environ.get('AIOSTEM_HOST', 'localhost')
10    port = int(os.environ.get('AIOSTEM_PORT', 9051))
11    connector = ControlConnectorPort(host, port)
12
13    print(f'[>] Connecting to {host} on port {port} (with a connector)')
14    async with Controller(connector) as ctrl:
15        reply = await ctrl.protocol_info()
16        reply.raise_for_status()
17        print(f'[+] Connected to Tor v{reply.data.tor_version}')
18
19if __name__ == '__main__':
20    asyncio.run(main())