Python interface for c-ares asynchronous DNS library
—
Traditional host resolution functions providing asynchronous versions of standard socket library operations. These functions offer familiar interfaces for hostname-to-address and address-to-hostname resolution with callback-based completion.
Resolve hostnames to IP addresses using traditional gethostbyname-style operations.
def gethostbyname(self, name: str, family, callback) -> None:
"""
Resolve a hostname to IP addresses for the specified address family.
Args:
name: str - Hostname to resolve
family: socket.AddressFamily - Address family (socket.AF_INET or socket.AF_INET6)
callback: Callable - Function called with (result, error) when complete
Raises:
TypeError: If callback is not callable
RuntimeError: If channel is destroyed
"""Usage Example:
import socket
import pycares
def host_callback(result, error):
if error:
print(f'Error: {pycares.errno.strerror(error)}')
return
print(f'Hostname: {result.name}')
print(f'Addresses: {result.addresses}')
if result.aliases:
print(f'Aliases: {result.aliases}')
channel = pycares.Channel()
# Resolve IPv4 addresses
channel.gethostbyname('google.com', socket.AF_INET, host_callback)
# Resolve IPv6 addresses
channel.gethostbyname('google.com', socket.AF_INET6, host_callback)Resolve IP addresses back to hostnames.
def gethostbyaddr(self, addr: str, callback) -> None:
"""
Resolve an IP address to hostname(s).
Args:
addr: str - IP address to resolve (IPv4 or IPv6)
callback: Callable - Function called with (result, error) when complete
Raises:
ValueError: If IP address format is invalid
TypeError: If callback is not callable
RuntimeError: If channel is destroyed
"""Usage Example:
def addr_callback(result, error):
if error:
print(f'Error: {pycares.errno.strerror(error)}')
return
print(f'Primary hostname: {result.name}')
if result.aliases:
print(f'Aliases: {result.aliases}')
print(f'Addresses: {result.addresses}')
channel = pycares.Channel()
# Reverse resolve IPv4 address
channel.gethostbyaddr('8.8.8.8', addr_callback)
# Reverse resolve IPv6 address
channel.gethostbyaddr('2001:4860:4860::8888', addr_callback)Modern getaddrinfo-style resolution supporting service names, protocol specifications, and comprehensive address information.
def getaddrinfo(
self,
host: str,
port, # Union[int, str, None]
callback,
family=0, # socket.AddressFamily
type=0, # int (socket type)
proto=0, # int (protocol)
flags=0 # int (AI_* flags)
) -> None:
"""
Resolve hostname and service to socket addresses with detailed information.
Args:
host: str - Hostname to resolve
port: Union[int, str, None] - Port number or service name (e.g., 'http', 80, None)
callback: Callable - Function called with (result, error) when complete
family: socket.AddressFamily - Address family filter (0 for any)
type: int - Socket type (socket.SOCK_STREAM, socket.SOCK_DGRAM, etc.)
proto: int - Protocol (socket.IPPROTO_TCP, socket.IPPROTO_UDP, etc.)
flags: int - Additional flags for resolution behavior
Raises:
TypeError: If callback is not callable
RuntimeError: If channel is destroyed
"""Usage Example:
import socket
def addrinfo_callback(result, error):
if error:
print(f'Error: {pycares.errno.strerror(error)}')
return
print('CNAME records:')
for cname in result.cnames:
print(f' {cname.alias} -> {cname.name} (TTL: {cname.ttl})')
print('Address records:')
for node in result.nodes:
addr = node.addr
if node.family == socket.AF_INET:
print(f' IPv4: {addr[0]}:{addr[1]} (TTL: {node.ttl})')
elif node.family == socket.AF_INET6:
print(f' IPv6: [{addr[0]}]:{addr[1]} (TTL: {node.ttl})')
channel = pycares.Channel()
# Resolve with port number
channel.getaddrinfo('google.com', 80, addrinfo_callback)
# Resolve with service name
channel.getaddrinfo('google.com', 'http', addrinfo_callback)
# Resolve with family filter
channel.getaddrinfo('google.com', 443, addrinfo_callback, family=socket.AF_INET6)
# Resolve without port
channel.getaddrinfo('google.com', None, addrinfo_callback)Resolve socket addresses back to hostname and service names.
def getnameinfo(self, address, flags: int, callback) -> None:
"""
Resolve socket address to hostname and service name.
Args:
address: Union[tuple[str, int], tuple[str, int, int, int]] - Socket address
IPv4: (host, port) tuple
IPv6: (host, port, flowinfo, scope_id) tuple
flags: int - Bitwise OR of ARES_NI_* flags controlling resolution behavior
callback: Callable - Function called with (result, error) when complete
Raises:
ValueError: If address format is invalid
TypeError: If callback is not callable
RuntimeError: If channel is destroyed
"""Usage Example:
def nameinfo_callback(result, error):
if error:
print(f'Error: {pycares.errno.strerror(error)}')
return
print(f'Hostname: {result.node}')
if result.service:
print(f'Service: {result.service}')
channel = pycares.Channel()
# Resolve IPv4 address and port
channel.getnameinfo(
('8.8.8.8', 53),
pycares.ARES_NI_LOOKUPHOST | pycares.ARES_NI_LOOKUPSERVICE,
nameinfo_callback
)
# Resolve IPv6 address
channel.getnameinfo(
('2001:4860:4860::8888', 53, 0, 0),
pycares.ARES_NI_LOOKUPHOST,
nameinfo_callback
)
# Get numeric host only
channel.getnameinfo(
('192.168.1.1', 80),
pycares.ARES_NI_NUMERICHOST | pycares.ARES_NI_NUMERICSERV,
nameinfo_callback
)Result object for gethostbyname and gethostbyaddr operations.
class ares_host_result:
"""Host resolution result."""
name: str # Primary hostname
aliases: list[str] # List of hostname aliases
addresses: list[str] # List of IP addressesComprehensive result object for getaddrinfo operations.
class ares_addrinfo_result:
"""Address info resolution result."""
cnames: list[ares_addrinfo_cname_result] # CNAME chain information
nodes: list[ares_addrinfo_node_result] # Address information nodes
class ares_addrinfo_cname_result:
"""CNAME information in address resolution."""
ttl: int # Time to live
alias: str # Alias name
name: str # Canonical name
class ares_addrinfo_node_result:
"""Individual address node in getaddrinfo result."""
ttl: int # Time to live
flags: int # Address flags
family: int # Address family (AF_INET or AF_INET6)
socktype: int # Socket type
protocol: int # Protocol number
addr: tuple # Address tuple (format depends on family)
# IPv4: (address_str, port)
# IPv6: (address_str, port, flowinfo, scope_id)Result object for getnameinfo operations.
class ares_nameinfo_result:
"""Name info resolution result."""
node: str # Hostname (may be None if not requested)
service: str # Service name (may be None if not requested or unavailable)Flags controlling getnameinfo behavior:
# Don't return fully qualified domain name
ARES_NI_NOFQDN = 1
# Return numeric host address instead of name
ARES_NI_NUMERICHOST = 2
# Require hostname (fail if not available)
ARES_NI_NAMEREQD = 4
# Return numeric service instead of name
ARES_NI_NUMERICSERV = 8
# Datagram service (affects service name lookup)
ARES_NI_DGRAM = 16
# TCP service
ARES_NI_TCP = 32
# UDP service
ARES_NI_UDP = 64
# SCTP service
ARES_NI_SCTP = 128
# DCCP service
ARES_NI_DCCP = 256
# Return numeric scope identifier
ARES_NI_NUMERICSCOPE = 512
# Lookup hostname
ARES_NI_LOOKUPHOST = 1024
# Lookup service name
ARES_NI_LOOKUPSERVICE = 2048
# Use IDNA encoding
ARES_NI_IDN = 4096
# Allow unassigned code points in IDNA
ARES_NI_IDN_ALLOW_UNASSIGNED = 8192
# Use STD3 ASCII rules for IDNA
ARES_NI_IDN_USE_STD3_ASCII_RULES = 16384Type definitions for address tuples:
# IPv4 address tuple: (address, port)
IP4 = tuple[str, int]
# IPv6 address tuple: (address, port, flowinfo, scope_id)
IP6 = tuple[str, int, int, int]import socket
import pycares
results = {}
pending = 0
def collect_result(name, result, error):
global pending
results[name] = (result, error)
pending -= 1
def resolve_multiple_hosts(channel, hostnames):
global pending
pending = len(hostnames)
for hostname in hostnames:
channel.gethostbyname(hostname, socket.AF_INET,
lambda r, e, h=hostname: collect_result(h, r, e))
# Wait for all to complete
while pending > 0:
wait_channel(channel)
return results
channel = pycares.Channel()
hosts = ['google.com', 'github.com', 'stackoverflow.com']
results = resolve_multiple_hosts(channel, hosts)
for host, (result, error) in results.items():
if error:
print(f'{host}: Error - {pycares.errno.strerror(error)}')
else:
print(f'{host}: {result.addresses[0]}')def discover_service(hostname, service_port):
"""Discover service endpoints for a hostname."""
def addrinfo_cb(result, error):
if error:
print(f'Service discovery failed: {pycares.errno.strerror(error)}')
return
print(f'Service endpoints for {hostname}:')
for node in result.nodes:
family = 'IPv4' if node.family == socket.AF_INET else 'IPv6'
addr_str = f'[{node.addr[0]}]' if node.family == socket.AF_INET6 else node.addr[0]
print(f' {family}: {addr_str}:{node.addr[1]} (TTL: {node.ttl})')
channel = pycares.Channel()
channel.getaddrinfo(hostname, service_port, addrinfo_cb,
type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP)
wait_channel(channel)
discover_service('www.google.com', 'https')Install with Tessl CLI
npx tessl i tessl/pypi-pycares