0
# Error Handling and Constants
1
2
Comprehensive error handling for DNS operations including error codes, utility functions, and debugging support. All pycares operations use consistent error reporting through callback functions and exception classes.
3
4
## Capabilities
5
6
### Error Codes
7
8
DNS resolution can fail for various reasons. pycares provides detailed error codes to help diagnose and handle different failure scenarios.
9
10
```python { .api }
11
# Success code
12
ARES_SUCCESS = 0
13
14
# DNS-specific errors
15
ARES_ENODATA = 1 # No data in DNS response
16
ARES_EFORMERR = 2 # DNS format error
17
ARES_ESERVFAIL = 3 # DNS server failure
18
ARES_ENOTFOUND = 4 # Domain name not found
19
ARES_ENOTIMP = 5 # Query type not implemented
20
ARES_EREFUSED = 6 # DNS query refused by server
21
ARES_EBADQUERY = 7 # Malformed DNS query
22
ARES_EBADNAME = 8 # Malformed domain name
23
ARES_EBADFAMILY = 9 # Bad address family
24
ARES_EBADRESP = 10 # Bad DNS response
25
ARES_ECONNREFUSED = 11 # Connection refused
26
ARES_ETIMEOUT = 12 # Query timeout
27
ARES_EOF = 13 # End of file
28
ARES_EFILE = 14 # File I/O error
29
ARES_ENOMEM = 15 # Out of memory
30
ARES_EDESTRUCTION = 16 # Channel destroyed
31
ARES_EBADSTR = 17 # Bad string
32
ARES_EBADFLAGS = 18 # Bad flags
33
ARES_ENONAME = 19 # No name
34
ARES_EBADHINTS = 20 # Bad hints
35
ARES_ENOTINITIALIZED = 21 # Not initialized
36
ARES_ELOADIPHLPAPI = 22 # Failed to load iphlpapi
37
ARES_EADDRGETNETWORKPARAMS = 23 # Failed to get network parameters
38
ARES_ECANCELLED = 24 # Query cancelled
39
ARES_ESERVICE = 25 # Service error
40
```
41
42
### Error Code Mapping
43
44
Mapping dictionary for converting error codes to symbolic names.
45
46
```python { .api }
47
errorcode: dict[int, str] # Maps error codes to string names
48
```
49
50
**Usage Example:**
51
52
```python
53
import pycares.errno
54
55
# Print all available error codes
56
for code, name in pycares.errno.errorcode.items():
57
print(f'{code}: {name}')
58
59
# Check if an error code is known
60
error_code = 4
61
if error_code in pycares.errno.errorcode:
62
print(f'Error {error_code} is {pycares.errno.errorcode[error_code]}')
63
```
64
65
### Error Messages
66
67
Convert error codes to human-readable error messages.
68
69
```python { .api }
70
def strerror(code: int) -> str:
71
"""
72
Get human-readable error message for an error code.
73
74
Args:
75
code: int - Error code from callback or exception
76
77
Returns:
78
str: Human-readable error message
79
"""
80
```
81
82
**Usage Example:**
83
84
```python
85
import pycares
86
import pycares.errno
87
88
def query_callback(result, error):
89
if error is not None:
90
error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
91
error_msg = pycares.errno.strerror(error)
92
print(f'DNS query failed: {error_name} - {error_msg}')
93
return
94
95
print(f'Query successful: {result}')
96
97
channel = pycares.Channel()
98
channel.query('nonexistent.invalid', pycares.QUERY_TYPE_A, query_callback)
99
```
100
101
## Exception Classes
102
103
### AresError
104
105
Primary exception class for c-ares related errors.
106
107
```python { .api }
108
class AresError(Exception):
109
"""
110
Exception raised for c-ares library errors.
111
112
This exception is raised for synchronous errors during channel creation,
113
configuration, or other immediate operations. Asynchronous query errors
114
are reported via callback functions, not exceptions.
115
"""
116
```
117
118
**Usage Example:**
119
120
```python
121
import pycares
122
123
try:
124
# This might raise AresError if initialization fails
125
channel = pycares.Channel(servers=['invalid.ip.address'])
126
except pycares.AresError as e:
127
print(f'Failed to create channel: {e}')
128
129
try:
130
channel = pycares.Channel()
131
# This might raise AresError if reinit fails
132
channel.reinit()
133
except pycares.AresError as e:
134
print(f'Failed to reinitialize channel: {e}')
135
```
136
137
## Error Handling Patterns
138
139
### Basic Error Handling
140
141
```python
142
import pycares
143
import pycares.errno
144
145
def handle_dns_result(result, error):
146
"""Standard DNS result handler with error checking."""
147
if error is not None:
148
if error == pycares.errno.ARES_ENOTFOUND:
149
print('Domain not found')
150
elif error == pycares.errno.ARES_ETIMEOUT:
151
print('Query timed out')
152
elif error == pycares.errno.ARES_ESERVFAIL:
153
print('DNS server failure')
154
else:
155
print(f'DNS error: {pycares.errno.strerror(error)}')
156
return
157
158
# Process successful result
159
print(f'DNS query successful: {result}')
160
161
channel = pycares.Channel()
162
channel.query('example.com', pycares.QUERY_TYPE_A, handle_dns_result)
163
```
164
165
### Retry Logic
166
167
```python
168
class DNSResolver:
169
def __init__(self, max_retries=3):
170
self.channel = pycares.Channel(timeout=5.0, tries=2)
171
self.max_retries = max_retries
172
173
def query_with_retry(self, name, query_type, callback, retries=0):
174
def retry_callback(result, error):
175
if error is not None and retries < self.max_retries:
176
# Retry on timeout or server failure
177
if error in (pycares.errno.ARES_ETIMEOUT, pycares.errno.ARES_ESERVFAIL):
178
print(f'Retrying query ({retries + 1}/{self.max_retries})')
179
self.query_with_retry(name, query_type, callback, retries + 1)
180
return
181
182
# Final result (success or non-retryable error)
183
callback(result, error)
184
185
self.channel.query(name, query_type, retry_callback)
186
187
resolver = DNSResolver()
188
resolver.query_with_retry('google.com', pycares.QUERY_TYPE_A, handle_dns_result)
189
```
190
191
### Error Categorization
192
193
```python
194
import pycares.errno
195
196
def categorize_error(error_code):
197
"""Categorize DNS errors for appropriate handling."""
198
if error_code == pycares.errno.ARES_SUCCESS:
199
return 'success'
200
elif error_code in (pycares.errno.ARES_ENOTFOUND, pycares.errno.ARES_ENODATA):
201
return 'not_found'
202
elif error_code in (pycares.errno.ARES_ETIMEOUT, pycares.errno.ARES_ECONNREFUSED):
203
return 'network'
204
elif error_code in (pycares.errno.ARES_ESERVFAIL, pycares.errno.ARES_EREFUSED):
205
return 'server'
206
elif error_code in (pycares.errno.ARES_EBADNAME, pycares.errno.ARES_EBADQUERY):
207
return 'invalid_input'
208
elif error_code == pycares.errno.ARES_ECANCELLED:
209
return 'cancelled'
210
else:
211
return 'unknown'
212
213
def handle_categorized_error(result, error):
214
if error is None:
215
print(f'Success: {result}')
216
return
217
218
category = categorize_error(error)
219
error_msg = pycares.errno.strerror(error)
220
221
if category == 'not_found':
222
print(f'Domain not found: {error_msg}')
223
elif category == 'network':
224
print(f'Network error (check connectivity): {error_msg}')
225
elif category == 'server':
226
print(f'DNS server error (try different server): {error_msg}')
227
elif category == 'invalid_input':
228
print(f'Invalid input (check domain name): {error_msg}')
229
elif category == 'cancelled':
230
print('Query was cancelled')
231
else:
232
print(f'Unknown error: {error_msg}')
233
```
234
235
### Timeout Handling
236
237
```python
238
import time
239
import threading
240
import pycares
241
242
class TimeoutHandler:
243
def __init__(self, channel, timeout_seconds=10):
244
self.channel = channel
245
self.timeout_seconds = timeout_seconds
246
self.active_queries = set()
247
248
def query_with_timeout(self, name, query_type, callback):
249
query_id = id(callback) # Simple query identifier
250
self.active_queries.add(query_id)
251
252
# Start timeout timer
253
def timeout_callback():
254
if query_id in self.active_queries:
255
self.active_queries.remove(query_id)
256
callback(None, pycares.errno.ARES_ETIMEOUT)
257
258
timer = threading.Timer(self.timeout_seconds, timeout_callback)
259
timer.start()
260
261
# Wrap original callback
262
def wrapped_callback(result, error):
263
if query_id in self.active_queries:
264
timer.cancel()
265
self.active_queries.remove(query_id)
266
callback(result, error)
267
268
self.channel.query(name, query_type, wrapped_callback)
269
270
# Usage
271
handler = TimeoutHandler(pycares.Channel(), timeout_seconds=5)
272
handler.query_with_timeout('slow-server.com', pycares.QUERY_TYPE_A, handle_dns_result)
273
```
274
275
## Debugging Support
276
277
### Verbose Error Logging
278
279
```python
280
import logging
281
import pycares.errno
282
283
logging.basicConfig(level=logging.DEBUG)
284
logger = logging.getLogger(__name__)
285
286
def debug_callback(name, query_type):
287
"""Create a callback with debug logging."""
288
def callback(result, error):
289
if error is not None:
290
error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
291
error_msg = pycares.errno.strerror(error)
292
logger.error(f'Query failed: {name} ({query_type}) - {error_name}: {error_msg}')
293
else:
294
logger.info(f'Query successful: {name} ({query_type}) - {len(result) if isinstance(result, list) else 1} records')
295
logger.debug(f'Result: {result}')
296
return callback
297
298
channel = pycares.Channel()
299
channel.query('google.com', pycares.QUERY_TYPE_A, debug_callback('google.com', 'A'))
300
```
301
302
### Error Statistics
303
304
```python
305
class ErrorStats:
306
def __init__(self):
307
self.error_counts = {}
308
self.success_count = 0
309
310
def record_result(self, error):
311
if error is None:
312
self.success_count += 1
313
else:
314
error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
315
self.error_counts[error_name] = self.error_counts.get(error_name, 0) + 1
316
317
def print_stats(self):
318
total = self.success_count + sum(self.error_counts.values())
319
print(f'DNS Query Statistics (Total: {total})')
320
print(f' Successful: {self.success_count}')
321
for error_name, count in sorted(self.error_counts.items()):
322
print(f' {error_name}: {count}')
323
324
stats = ErrorStats()
325
326
def stats_callback(result, error):
327
stats.record_result(error)
328
if error is None:
329
print(f'Success: {result}')
330
else:
331
print(f'Error: {pycares.errno.strerror(error)}')
332
333
# After running queries...
334
stats.print_stats()
335
```
336
337
## Common Error Scenarios
338
339
### Network Connectivity Issues
340
341
```python
342
# DNS server unreachable
343
# Error: ARES_ECONNREFUSED or ARES_ETIMEOUT
344
345
# Firewall blocking DNS
346
# Error: ARES_ETIMEOUT
347
348
# No internet connection
349
# Error: ARES_ECONNREFUSED or ARES_ETIMEOUT
350
```
351
352
### DNS Configuration Problems
353
354
```python
355
# Invalid DNS server configured
356
# Error: ARES_ESERVFAIL or ARES_ETIMEOUT
357
358
# DNS server not responding
359
# Error: ARES_ETIMEOUT
360
361
# DNS server refusing queries
362
# Error: ARES_EREFUSED
363
```
364
365
### Query Issues
366
367
```python
368
# Domain doesn't exist
369
# Error: ARES_ENOTFOUND
370
371
# Record type doesn't exist for domain
372
# Error: ARES_ENODATA
373
374
# Malformed domain name
375
# Error: ARES_EBADNAME
376
377
# Invalid query parameters
378
# Error: ARES_EBADQUERY
379
```
380
381
All error handling in pycares follows the pattern of passing error codes to callback functions rather than raising exceptions for asynchronous operations. Synchronous configuration errors raise AresError exceptions.