Python interface for c-ares asynchronous DNS library
—
Core functionality for creating, configuring, and managing DNS resolution channels. The Channel class serves as the primary interface for all DNS operations in pycares.
Create a new DNS resolution channel with comprehensive configuration options for controlling query behavior, server selection, and network settings.
class Channel:
def __init__(
self,
flags=None, # Optional[int]: Channel behavior flags
timeout=None, # Optional[float]: Query timeout in seconds
tries=None, # Optional[int]: Number of query attempts
ndots=None, # Optional[int]: Number of dots for domain search
tcp_port=None, # Optional[int]: TCP port for DNS queries
udp_port=None, # Optional[int]: UDP port for DNS queries
servers=None, # Optional[Iterable[Union[str, bytes]]]: DNS servers list
domains=None, # Optional[Iterable[Union[str, bytes]]]: Search domains
lookups=None, # Union[str, bytes, None]: Lookup order
sock_state_cb=None, # Optional[Callable[[int, bool, bool], None]]: Socket state callback
socket_send_buffer_size=None, # Optional[int]: Send buffer size
socket_receive_buffer_size=None, # Optional[int]: Receive buffer size
rotate=False, # bool: Rotate server list
local_ip=None, # Union[str, bytes, None]: Local IP binding
local_dev=None, # Optional[str]: Local device binding
resolvconf_path=None, # Union[str, bytes, None]: Custom resolv.conf path
event_thread=False # bool: Use event thread mode
):
"""
Create a new DNS resolution channel.
Args:
flags: Bitwise OR of ARES_FLAG_* constants for channel behavior
timeout: Query timeout in seconds (default uses system settings)
tries: Number of times to try each query (default: 4)
ndots: Number of dots needed in name for initial absolute query
tcp_port: TCP port number for DNS queries (default: 53)
udp_port: UDP port number for DNS queries (default: 53)
servers: List of DNS server IP addresses as strings
domains: List of search domains for unqualified names
lookups: String specifying lookup order (e.g., "bf" for bind then file)
sock_state_cb: Callback for socket state changes (fd, readable, writable)
socket_send_buffer_size: SO_SNDBUF socket option value
socket_receive_buffer_size: SO_RCVBUF socket option value
rotate: Whether to rotate through servers for load balancing
local_ip: Local IP address to bind outgoing queries to
local_dev: Local network device to bind to
resolvconf_path: Path to custom resolv.conf file
event_thread: Enable built-in event thread (requires thread-safe c-ares)
Raises:
AresError: If channel initialization fails
RuntimeError: If event_thread=True but c-ares not thread-safe
"""Usage Example:
import pycares
# Basic channel with custom servers
channel = pycares.Channel(
servers=['8.8.8.8', '8.8.4.4'],
timeout=5.0,
tries=2
)
# Advanced channel configuration
channel = pycares.Channel(
flags=pycares.ARES_FLAG_USEVC | pycares.ARES_FLAG_IGNTC,
servers=['1.1.1.1', '1.0.0.1'],
domains=['example.com', 'test.org'],
timeout=3.0,
tries=3,
rotate=True
)Functions for integrating DNS channel I/O with external event loops and monitoring systems. These functions enable non-blocking operation by allowing the application to monitor and process socket events.
def getsock(self):
"""
Get socket file descriptors for I/O monitoring.
Returns:
tuple[list[int], list[int]]: (read_fds, write_fds) - Lists of socket
file descriptors ready for reading and writing
"""
def process_fd(self, read_fd: int, write_fd: int) -> None:
"""
Process I/O events on specific file descriptors.
Args:
read_fd: File descriptor ready for reading (or ARES_SOCKET_BAD if none)
write_fd: File descriptor ready for writing (or ARES_SOCKET_BAD if none)
"""
def process_read_fd(self, read_fd: int) -> None:
"""
Process read events on a file descriptor.
Args:
read_fd: File descriptor ready for reading
"""
def process_write_fd(self, write_fd: int) -> None:
"""
Process write events on a file descriptor.
Args:
write_fd: File descriptor ready for writing
"""
def timeout(self, t=None):
"""
Get or set timeout for next I/O operation.
Args:
t: Optional[float] - Maximum timeout in seconds, or None for no limit
Returns:
float: Actual timeout value for next operation in seconds
Raises:
ValueError: If timeout is negative
"""Usage Example:
import select
import pycares
def wait_channel(channel):
"""Process all pending DNS operations until completion."""
while True:
read_fds, write_fds = channel.getsock()
if not read_fds and not write_fds:
break
timeout = channel.timeout()
if timeout == 0.0:
# No timeout needed, process immediately
channel.process_fd(pycares.ARES_SOCKET_BAD, pycares.ARES_SOCKET_BAD)
continue
# Wait for I/O events
rlist, wlist, xlist = select.select(read_fds, write_fds, [], timeout)
# Process ready file descriptors
for fd in rlist:
channel.process_read_fd(fd)
for fd in wlist:
channel.process_write_fd(fd)Manage the list of DNS servers used by the channel for query resolution.
@property
def servers(self) -> list[str]:
"""
Get the current list of DNS servers.
Returns:
list[str]: List of DNS server IP addresses
Raises:
AresError: If unable to retrieve server list
"""
@servers.setter
def servers(self, servers) -> None:
"""
Set the list of DNS servers for the channel.
Args:
servers: Iterable[Union[str, bytes]] - IP addresses of DNS servers
Raises:
ValueError: If any IP address is invalid
AresError: If unable to set servers
"""Usage Example:
channel = pycares.Channel()
# Get current servers
current_servers = channel.servers
print(f"Current servers: {current_servers}")
# Set new servers
channel.servers = ['8.8.8.8', '8.8.4.4', '1.1.1.1']
# Add IPv6 servers
channel.servers = ['2001:4860:4860::8888', '2001:4860:4860::8844']Functions for managing channel lifecycle and query operations.
def cancel(self) -> None:
"""
Cancel all pending queries on this channel.
All pending query callbacks will be called with ARES_ECANCELLED error.
"""
def reinit(self) -> None:
"""
Reinitialize the channel with current system DNS configuration.
Rereads system DNS settings (resolv.conf, registry, etc.) and applies
them to the channel.
Raises:
AresError: If reinitialization fails
"""
def close(self) -> None:
"""
Close the channel and clean up resources.
This method schedules safe channel destruction in a background thread.
Once called, no new queries can be started. Pending queries will be
cancelled and their callbacks will receive ARES_ECANCELLED.
This method is thread-safe and can be called multiple times.
"""Configure local network interface binding for outgoing DNS queries.
def set_local_ip(self, ip) -> None:
"""
Set the local IP address to bind outgoing queries to.
Args:
ip: Union[str, bytes] - IPv4 or IPv6 address to bind to
Raises:
ValueError: If IP address is invalid
"""
def set_local_dev(self, dev) -> None:
"""
Set the local network device to bind outgoing queries to.
Args:
dev: str - Network device name (e.g., 'eth0', 'wlan0')
"""Usage Example:
channel = pycares.Channel()
# Bind to specific IP address
channel.set_local_ip('192.168.1.100')
# Bind to specific network interface
channel.set_local_dev('eth0')
# Perform queries - they will use the bound interface
channel.query('google.com', pycares.QUERY_TYPE_A, callback)Configuration flags that control channel behavior:
# Use TCP instead of UDP for queries
ARES_FLAG_USEVC = 1
# Only query primary DNS servers
ARES_FLAG_PRIMARY = 2
# Ignore truncated responses
ARES_FLAG_IGNTC = 4
# Don't perform recursive queries
ARES_FLAG_NORECURSE = 8
# Keep TCP connections open for multiple queries
ARES_FLAG_STAYOPEN = 16
# Don't use search domains for single-label names
ARES_FLAG_NOSEARCH = 32
# Don't look up aliases
ARES_FLAG_NOALIASES = 64
# Don't check response against query
ARES_FLAG_NOCHECKRESP = 128
# Enable EDNS (Extension Mechanisms for DNS)
ARES_FLAG_EDNS = 256
# Don't add default servers if none specified
ARES_FLAG_NO_DFLT_SVR = 512For advanced event loop integration, channels can be configured with a socket state callback:
def socket_callback(fd, readable, writable):
"""
Socket state change callback.
Args:
fd: int - Socket file descriptor
readable: bool - Whether socket is ready for reading
writable: bool - Whether socket is ready for writing
"""
if readable:
# Add fd to read monitoring
loop.add_reader(fd, process_read, fd)
if writable:
# Add fd to write monitoring
loop.add_writer(fd, process_write, fd)
if not readable and not writable:
# Socket closed, remove from monitoring
loop.remove_reader(fd)
loop.remove_writer(fd)
channel = pycares.Channel(sock_state_cb=socket_callback)Check if the underlying c-ares library supports thread-safe operation:
def ares_threadsafety() -> bool:
"""
Check if c-ares was compiled with thread safety support.
Returns:
bool: True if thread-safe, False otherwise
"""When thread safety is available, you can use event_thread=True in Channel constructor to enable built-in event processing without manual I/O monitoring.
Install with Tessl CLI
npx tessl i tessl/pypi-pycares