04. Events

Events are, besides of commands the other major way to get data from Tor. You can subscribe to events and get asynchronous notifications until you disable the event.

List of events

The following code displays the list of all events supported by your Tor:

examples/events_list.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4import os
 5from aiostem import Controller
 6
 7async def main():
 8    password = os.environ.get('AIOSTEM_PASS', 'password')
 9    host = os.environ.get('AIOSTEM_HOST', 'localhost')
10    port = os.environ.get('AIOSTEM_PORT', 9051)
11
12    print(f'[>] Connecting to {host} on port {port}')
13    async with Controller.from_port(host, int(port)) as ctrl:
14        reply = await ctrl.authenticate(password)
15        reply.raise_for_status()
16
17        reply = await ctrl.get_info('events/names')
18        reply.raise_for_status()
19
20        names = sorted(reply['events/names'].split(' '))
21        print(f'Listing {len(names)} events:')
22        for name in names:
23            print(f'- {name}')
24
25if __name__ == '__main__':
26    asyncio.run(main())

All events handled by this library are described on EventWord. Additionally, we also have support for events that are internal to this library, documented on EventWordInternal. Currently only DISCONNECT is supported, providing a way to be notified when the controller has been disconnected from the remote Tor daemon (this may happen when Tor is shutting down for example).

Subscribe to events

Event subscription and unsubscription are handled by add_event_handler() and del_event_handler(). These functions register a callback you provide, and ask Tor for the associated events using set_events(). Do not call this method by yourself, as this is supposed to be handled by our event manager.

The following example generates events on demand through resolve(), which performs a DNS resolution for the provided domain(s). Its results cannot be provided back immediately, so they are provided as an ADDRMAP, parsed through EventAddrMap.

examples/events_resolve.py
 1#!/usr/bin/env python
 2
 3import asyncio
 4import os
 5import sys
 6from aiostem import Controller
 7from aiostem.event import EventAddrMap
 8from functools import partial
 9
10def on_addrmap(done, event):
11    if isinstance(event, EventAddrMap):
12        print(f'{event.original} is located at {event.replacement}')
13        done.set()
14
15async def main():
16    password = os.environ.get('AIOSTEM_PASS', 'password')
17    host = os.environ.get('AIOSTEM_HOST', 'localhost')
18    port = os.environ.get('AIOSTEM_PORT', 9051)
19
20    # Simple asyncio event to exit when the event has been received.
21    done = asyncio.Event()
22
23    print(f'[>] Connecting to {host} on port {port}')
24    async with Controller.from_port(host, int(port)) as ctrl:
25        reply = await ctrl.authenticate(password)
26        reply.raise_for_status()
27
28        await ctrl.add_event_handler('ADDRMAP', partial(on_addrmap, done))
29        reply = await ctrl.resolve(sys.argv[1:])
30        reply.raise_for_status()
31
32        # Wait until the address is resolved.
33        await done.wait()
34
35if __name__ == '__main__':
36    asyncio.run(main())

Its intended use is as follow:

$ python examples/events_resolve.py google.com
[>] Connecting to localhost on port 9051
google.com is located at 142.251.39.110

The callback method provided here can be either synchronous or asynchronous, but you need to take extra care here since the callback methods run directly from the stream reader task. If you need extra time, consider putting items in an asyncio.Queue and handle events in a separate task.