or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# aiodns

1

2

Simple DNS resolver for asyncio that provides asynchronous DNS resolution capabilities using the pycares library. aiodns enables non-blocking DNS queries in Python applications using async/await syntax, supporting all major DNS record types and offering efficient hostname resolution with /etc/hosts support.

3

4

## Package Information

5

6

- **Package Name**: aiodns

7

- **Version**: 3.5.0

8

- **Language**: Python

9

- **Installation**: `pip install aiodns`

10

- **Dependencies**: `pycares>=4.9.0`

11

- **Python Version**: 3.9+

12

13

## Core Imports

14

15

```python

16

import aiodns

17

```

18

19

Common usage pattern:

20

21

```python

22

from aiodns import DNSResolver

23

```

24

25

Access to error constants and types:

26

27

```python

28

from aiodns import error

29

from aiodns.error import DNSError, ARES_ENOTFOUND, ARES_ETIMEOUT

30

import socket

31

import asyncio

32

from types import TracebackType

33

from typing import Optional, Sequence, Union, Iterable, Literal, Any

34

import pycares

35

```

36

37

## Basic Usage

38

39

```python

40

import asyncio

41

import aiodns

42

43

async def main():

44

# Create a DNS resolver

45

resolver = aiodns.DNSResolver()

46

47

try:

48

# Perform A record query (returns list[pycares.ares_query_a_result])

49

a_result = await resolver.query('google.com', 'A')

50

print(f"A records: {[r.host for r in a_result]}")

51

52

# Perform AAAA record query (returns list[pycares.ares_query_aaaa_result])

53

aaaa_result = await resolver.query('google.com', 'AAAA')

54

print(f"IPv6 addresses: {[r.host for r in aaaa_result]}")

55

56

# CNAME query returns single result (not a list)

57

cname_result = await resolver.query('www.github.com', 'CNAME')

58

print(f"CNAME: {cname_result.cname}")

59

60

# MX query returns list of results

61

mx_result = await resolver.query('github.com', 'MX')

62

print(f"MX records: {[(r.host, r.priority) for r in mx_result]}")

63

64

# Hostname resolution with /etc/hosts support

65

import socket

66

host_result = await resolver.gethostbyname('localhost', socket.AF_INET)

67

print(f"Localhost IP: {host_result.addresses}")

68

69

except aiodns.error.DNSError as e:

70

print(f"DNS error: {e}")

71

finally:

72

# Clean up resources

73

await resolver.close()

74

75

# Using async context manager (automatic cleanup)

76

async def context_example():

77

async with aiodns.DNSResolver() as resolver:

78

result = await resolver.query('example.com', 'A')

79

print(f"IP addresses: {[r.host for r in result]}")

80

# resolver.close() called automatically

81

82

asyncio.run(main())

83

```

84

85

## Capabilities

86

87

### DNS Query Operations

88

89

Perform DNS queries for various record types with full pycares integration.

90

91

```python { .api }

92

class DNSResolver:

93

def __init__(

94

self,

95

nameservers: Optional[Sequence[str]] = None,

96

loop: Optional[asyncio.AbstractEventLoop] = None,

97

**kwargs: Any

98

) -> None:

99

"""

100

Create a DNS resolver instance.

101

102

Parameters:

103

- nameservers: Optional list of DNS server IP addresses

104

- loop: Optional asyncio event loop (defaults to current loop)

105

- **kwargs: Additional options passed to pycares.Channel

106

"""

107

108

# Type-specific query method overloads

109

def query(self, host: str, qtype: Literal['A'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_a_result]]:

110

"""Query A records (IPv4 addresses)."""

111

112

def query(self, host: str, qtype: Literal['AAAA'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_aaaa_result]]:

113

"""Query AAAA records (IPv6 addresses)."""

114

115

def query(self, host: str, qtype: Literal['CAA'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_caa_result]]:

116

"""Query CAA records (Certificate Authority Authorization)."""

117

118

def query(self, host: str, qtype: Literal['CNAME'], qclass: Optional[str] = None) -> asyncio.Future[pycares.ares_query_cname_result]:

119

"""Query CNAME record (Canonical Name) - returns single result."""

120

121

def query(self, host: str, qtype: Literal['MX'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_mx_result]]:

122

"""Query MX records (Mail Exchange)."""

123

124

def query(self, host: str, qtype: Literal['NAPTR'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_naptr_result]]:

125

"""Query NAPTR records (Name Authority Pointer)."""

126

127

def query(self, host: str, qtype: Literal['NS'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_ns_result]]:

128

"""Query NS records (Name Server)."""

129

130

def query(self, host: str, qtype: Literal['PTR'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_ptr_result]]:

131

"""Query PTR records (Pointer)."""

132

133

def query(self, host: str, qtype: Literal['SOA'], qclass: Optional[str] = None) -> asyncio.Future[pycares.ares_query_soa_result]:

134

"""Query SOA record (Start of Authority) - returns single result."""

135

136

def query(self, host: str, qtype: Literal['SRV'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_srv_result]]:

137

"""Query SRV records (Service)."""

138

139

def query(self, host: str, qtype: Literal['TXT'], qclass: Optional[str] = None) -> asyncio.Future[list[pycares.ares_query_txt_result]]:

140

"""Query TXT records (Text)."""

141

142

# Generic query method for runtime use

143

def query(self, host: str, qtype: str, qclass: Optional[str] = None) -> Union[asyncio.Future[list[Any]], asyncio.Future[Any]]:

144

"""

145

Perform DNS query for specified record type.

146

147

Parameters:

148

- host: Hostname or domain to query

149

- qtype: Query type ('A', 'AAAA', 'CNAME', 'MX', 'TXT', 'PTR', 'SOA', 'SRV', 'NS', 'CAA', 'NAPTR', 'ANY')

150

- qclass: Query class ('IN', 'CHAOS', 'HS', 'NONE', 'ANY'), defaults to 'IN'

151

152

Returns:

153

- asyncio.Future with query results (type varies by query type)

154

- Most queries return list[ResultType], but CNAME and SOA return single ResultType

155

156

Raises:

157

- ValueError: Invalid query type or class

158

- DNSError: DNS resolution errors

159

"""

160

```

161

162

### Hostname Resolution

163

164

Resolve hostnames to IP addresses with /etc/hosts file support.

165

166

```python { .api }

167

def gethostbyname(

168

self,

169

host: str,

170

family: socket.AddressFamily

171

) -> asyncio.Future[pycares.ares_host_result]:

172

"""

173

Resolve hostname to IP address, checking /etc/hosts first.

174

175

Parameters:

176

- host: Hostname to resolve

177

- family: Address family (socket.AF_INET or socket.AF_INET6)

178

179

Returns:

180

- asyncio.Future[pycares.ares_host_result] with host information

181

"""

182

183

def getaddrinfo(

184

self,

185

host: str,

186

family: socket.AddressFamily = socket.AF_UNSPEC,

187

port: Optional[int] = None,

188

proto: int = 0,

189

type: int = 0,

190

flags: int = 0

191

) -> asyncio.Future[pycares.ares_addrinfo_result]:

192

"""

193

Get address information for hostname (async equivalent of socket.getaddrinfo).

194

195

Parameters:

196

- host: Hostname to resolve

197

- family: Address family (socket.AF_INET, socket.AF_INET6, or socket.AF_UNSPEC)

198

- port: Port number (optional)

199

- proto: Protocol (0 for any)

200

- type: Socket type (0 for any)

201

- flags: Additional flags

202

203

Returns:

204

- asyncio.Future[pycares.ares_addrinfo_result] with address information

205

"""

206

```

207

208

### Reverse DNS Lookup

209

210

Perform reverse DNS lookups from IP addresses to hostnames.

211

212

```python { .api }

213

def gethostbyaddr(self, name: str) -> asyncio.Future[pycares.ares_host_result]:

214

"""

215

Perform reverse DNS lookup from IP address to hostname.

216

217

Parameters:

218

- name: IP address string to resolve

219

220

Returns:

221

- asyncio.Future[pycares.ares_host_result] with hostname information

222

"""

223

224

def getnameinfo(

225

self,

226

sockaddr: Union[tuple[str, int], tuple[str, int, int, int]],

227

flags: int = 0

228

) -> asyncio.Future[pycares.ares_nameinfo_result]:

229

"""

230

Reverse name resolution from socket address.

231

232

Parameters:

233

- sockaddr: Socket address tuple (IPv4: (host, port), IPv6: (host, port, flow, scope))

234

- flags: Additional flags

235

236

Returns:

237

- asyncio.Future[pycares.ares_nameinfo_result] with name information

238

"""

239

```

240

241

### Resource Management

242

243

Control resolver lifecycle and cancel operations.

244

245

```python { .api }

246

@property

247

def nameservers(self) -> Sequence[str]:

248

"""Get current DNS nameservers."""

249

250

@nameservers.setter

251

def nameservers(self, value: Iterable[Union[str, bytes]]) -> None:

252

"""Set DNS nameservers."""

253

254

def cancel(self) -> None:

255

"""Cancel all pending DNS queries. All futures will receive DNSError with ARES_ECANCELLED."""

256

257

async def close(self) -> None:

258

"""

259

Cleanly close the DNS resolver and release all resources.

260

Must be called when resolver is no longer needed.

261

"""

262

263

def __del__(self) -> None:

264

"""

265

Handle cleanup when the resolver is garbage collected.

266

Automatically calls _cleanup() to release resources if resolver was not properly closed.

267

Note: Explicit close() is preferred over relying on garbage collection.

268

"""

269

```

270

271

### Async Context Manager Support

272

273

Automatic resource cleanup using async context manager pattern.

274

275

```python { .api }

276

async def __aenter__(self) -> 'DNSResolver':

277

"""Enter async context manager."""

278

279

async def __aexit__(

280

self,

281

exc_type: Optional[type[BaseException]],

282

exc_val: Optional[BaseException],

283

exc_tb: Optional[TracebackType]

284

) -> None:

285

"""Exit async context manager, automatically calls close()."""

286

```

287

288

### Error Handling

289

290

DNS errors and status codes from the underlying pycares library.

291

292

```python { .api }

293

class DNSError(Exception):

294

"""Base class for all DNS errors."""

295

296

# Error constants (integers)

297

ARES_SUCCESS: int

298

ARES_ENOTFOUND: int # Domain name not found

299

ARES_EFORMERR: int # Format error

300

ARES_ESERVFAIL: int # Server failure

301

ARES_ENOTINITIALIZED: int # Not initialized

302

ARES_EBADNAME: int # Bad domain name

303

ARES_ETIMEOUT: int # Timeout

304

ARES_ECONNREFUSED: int # Connection refused

305

ARES_ENOMEM: int # Out of memory

306

ARES_ECANCELLED: int # Query cancelled

307

ARES_EBADQUERY: int # Bad query

308

ARES_EBADRESP: int # Bad response

309

ARES_ENODATA: int # No data

310

ARES_EBADFAMILY: int # Bad address family

311

ARES_EBADFLAGS: int # Bad flags

312

ARES_EBADHINTS: int # Bad hints

313

ARES_EBADSTR: int # Bad string

314

ARES_EREFUSED: int # Refused

315

ARES_ENOTIMP: int # Not implemented

316

ARES_ESERVICE: int # Service error

317

ARES_EFILE: int # File error

318

ARES_EOF: int # End of file

319

ARES_EDESTRUCTION: int # Destruction

320

ARES_ELOADIPHLPAPI: int # Load IP helper API error

321

ARES_EADDRGETNETWORKPARAMS: int # Address get network params error

322

```

323

324

## Types

325

326

Query result types vary by DNS record type:

327

328

```python { .api }

329

# A record results

330

pycares.ares_query_a_result:

331

host: str # IPv4 address

332

333

# AAAA record results

334

pycares.ares_query_aaaa_result:

335

host: str # IPv6 address

336

337

# CNAME record result

338

pycares.ares_query_cname_result:

339

cname: str # Canonical name

340

341

# MX record results

342

pycares.ares_query_mx_result:

343

host: str # Mail server hostname

344

priority: int # Priority value

345

346

# TXT record results

347

pycares.ares_query_txt_result:

348

text: bytes # Text record data

349

350

# PTR record results

351

pycares.ares_query_ptr_result:

352

name: str # Pointer target name

353

354

# SOA record result

355

pycares.ares_query_soa_result:

356

nsname: str # Primary nameserver

357

hostmaster: str # Responsible person email

358

serial: int # Serial number

359

refresh: int # Refresh interval

360

retry: int # Retry interval

361

expires: int # Expiry time

362

minttl: int # Minimum TTL

363

364

# SRV record results

365

pycares.ares_query_srv_result:

366

host: str # Target hostname

367

port: int # Port number

368

priority: int # Priority

369

weight: int # Weight

370

371

# NS record results

372

pycares.ares_query_ns_result:

373

host: str # Nameserver hostname

374

375

# CAA record results

376

pycares.ares_query_caa_result:

377

critical: int # Critical flag

378

property: bytes # Property name

379

value: bytes # Property value

380

381

# NAPTR record results

382

pycares.ares_query_naptr_result:

383

order: int # Order value

384

preference: int # Preference value

385

flags: bytes # Flags

386

service: bytes # Service

387

regexp: bytes # Regular expression

388

replacement: bytes # Replacement

389

390

# Host resolution results

391

pycares.ares_host_result:

392

name: str # Primary hostname

393

aliases: List[str] # Hostname aliases

394

addresses: List[str] # IP addresses

395

396

# Address info results

397

pycares.ares_addrinfo_result:

398

nodes: List[ares_addrinfo_node] # Address information nodes

399

400

# Address info node

401

pycares.ares_addrinfo_node:

402

family: int # Address family (socket.AF_INET, socket.AF_INET6)

403

socktype: int # Socket type

404

protocol: int # Protocol

405

addr: tuple # Address tuple (host, port) or (host, port, flow, scope)

406

canonname: str # Canonical name (optional)

407

408

# Name info results

409

pycares.ares_nameinfo_result:

410

node: str # Hostname

411

service: str # Service name

412

```

413

414

## Platform Notes

415

416

### Windows Compatibility

417

418

aiodns requires `asyncio.SelectorEventLoop` or `winloop` on Windows when using custom pycares builds without thread-safety. Official PyPI wheels (pycares 4.7.0+) include thread-safe c-ares and work with any event loop.

419

420

```python

421

# For non-thread-safe pycares builds on Windows:

422

import asyncio

423

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

424

```

425

426

### Thread Safety

427

428

aiodns automatically detects thread-safe pycares builds and optimizes accordingly:

429

- Thread-safe builds: Uses pycares event thread for better performance

430

- Non-thread-safe builds: Falls back to socket state callbacks with asyncio integration