Pycares: Asynchronous DNS Resolver
Pycares is a Python module that provides an asynchronous interface to c-ares, a C library for performing DNS requests and name resolutions. It enables non-blocking DNS lookups, making it suitable for high-performance network applications. The library is actively maintained, currently at version 5.0.1, with regular releases addressing bug fixes and introducing new features.
Warnings
- breaking The DNS query results API was completely rewritten in v5.0.0. Results are now returned as structured dataclasses (`DNSResult`, `DNSRecord`, and specific `RecordData` types like `ARecordData`, `MXRecordData`, etc.) instead of a list of record-specific objects. Existing code accessing results will break.
- breaking In v5.0.0, the `Channel` constructor arguments and the `callback` parameter for query methods are now strictly keyword-only. The `event_thread` parameter has also been removed, as event thread mode is now implicit.
- breaking As of v5.0.0, TXT record data is returned as bytes instead of strings. This change affects how TXT record content should be handled.
- breaking Pycares v5.0.0 switched its build system for the bundled c-ares library to CMake. Building from source now requires CMake version 3.5 or higher to be installed on the system.
- gotcha Improper management of `pycares.Channel` objects, particularly allowing them to be garbage collected while DNS queries are still pending, can lead to a use-after-free vulnerability, causing a fatal Python interpreter crash.
- gotcha DNS queries made by `pycares` are real network operations. Consequently, tests and examples often require active internet access and can be sensitive to network conditions or DNS server configurations, potentially leading to environment-specific failures.
Install
-
pip install pycares -
pip install pycares[idna]
Imports
- pycares
import pycares
Quickstart
import pycares
import socket
def callback(result, error):
if error:
print(f"Error: {error}")
return
if result:
for record in result.answer:
if record.type == pycares.QUERY_TYPE_A:
print(f"A record for {record.name}: {record.data.addr}")
elif record.type == pycares.QUERY_TYPE_AAAA:
print(f"AAAA record for {record.name}: {record.data.addr}")
elif record.type == pycares.QUERY_TYPE_MX:
print(f"MX record for {record.name}: priority={record.data.priority}, exchange={record.data.exchange}")
# Add other record types as needed
else:
print("No records found.")
# Using a simple select-based event loop
channel = pycares.Channel(timeout=5.0)
# Query for A records
channel.query("google.com", pycares.QUERY_TYPE_A, callback=callback)
# Query for MX records
channel.query("example.com", pycares.QUERY_TYPE_MX, callback=callback)
# Basic event loop processing
while True:
read_fds, write_fds = channel.getsockname()
if not read_fds and not write_fds:
break
# In a real application, use an actual event loop (e.g., asyncio, Tornado, Gevent)
# For this simple example, we block briefly
try:
rlist, wlist, xlist = socket.select(read_fds, write_fds, [], 1.0)
except socket.error as e:
print(f"Socket error in select: {e}")
break
channel.process_fd(rlist, wlist)