0
# DNS Client Utilities
1
2
DiG-like DNS client functionality provided as a command-line tool for querying DNS servers, comparing responses, and debugging DNS configurations. The primary interface is through the command-line utility, with programmatic access available through the core DNSRecord API.
3
4
## Capabilities
5
6
### Programmatic DNS Queries
7
8
DNS queries can be performed programmatically using the core DNSRecord.send() method and related functionality.
9
10
```python { .api }
11
# Core query functionality (from dnslib.dns)
12
def send(self, dest, port=53, tcp=False, timeout=None, ipv6=False):
13
"""
14
Send DNS query and return response packet.
15
Available on DNSRecord instances.
16
17
Args:
18
dest (str): Destination server address
19
port (int, optional): Destination port (default: 53)
20
tcp (bool, optional): Use TCP instead of UDP (default: False)
21
timeout (int, optional): Query timeout in seconds (default: None)
22
ipv6 (bool, optional): Use IPv6 (default: False)
23
24
Returns:
25
bytes: DNS response packet in wire format
26
"""
27
```
28
29
### DiG Output Parser
30
31
Integration with DiG output parsing for response comparison and validation.
32
33
```python { .api }
34
# From dnslib.digparser
35
class DigParser:
36
"""
37
Parser for DiG textual output format.
38
Enables comparison between dnslib and DiG responses.
39
"""
40
def parse(self, dig_output):
41
"""
42
Parse DiG output into DNSRecord objects.
43
44
Args:
45
dig_output (str): DiG command output text
46
47
Returns:
48
list[DNSRecord]: Parsed DNS records
49
"""
50
```
51
52
## Command Line Interface
53
54
The dnslib.client module provides a comprehensive DiG-like command-line interface for DNS queries.
55
56
### Basic Usage
57
58
```bash
59
# Query A record for domain
60
python -m dnslib.client example.com
61
62
# Query specific record type
63
python -m dnslib.client example.com MX
64
65
# Query specific server
66
python -m dnslib.client --server 8.8.8.8 example.com
67
68
# Use TCP protocol
69
python -m dnslib.client --tcp example.com
70
71
# Enable DNSSEC
72
python -m dnslib.client --dnssec example.com
73
74
# Show query packet
75
python -m dnslib.client --query example.com
76
77
# Show packet in hex
78
python -m dnslib.client --hex example.com
79
80
# Short output (rdata only)
81
python -m dnslib.client --short example.com
82
```
83
84
### Advanced Options
85
86
```bash
87
# Compare responses from different servers
88
python -m dnslib.client --diff 1.1.1.1 --server 8.8.8.8 example.com
89
90
# Compare with DiG output
91
python -m dnslib.client --dig example.com
92
93
# Combine comparison and DiG
94
python -m dnslib.client --diff 1.1.1.1 --dig example.com
95
96
# Disable TCP fallback for truncated responses
97
python -m dnslib.client --noretry example.com
98
99
# Debug mode - drop into CLI after request
100
python -m dnslib.client --debug example.com
101
```
102
103
### Command Line Parameters
104
105
```bash
106
python -m dnslib.client [OPTIONS] <domain> [<type>]
107
108
Options:
109
--server, -s ADDRESS:PORT Server address:port (default: 8.8.8.8:53)
110
--query Show query packet
111
--hex Dump packet in hex format
112
--tcp Use TCP instead of UDP
113
--noretry Don't retry with TCP if truncated
114
--diff ADDRESS:PORT Compare with alternate nameserver
115
--dig Compare result with DiG
116
--short Short output - rdata only
117
--dnssec Set DNSSEC (DO/AD) flags
118
--debug Drop into CLI after request
119
```
120
121
## Usage Examples
122
123
### Basic Programmatic Queries
124
125
```python
126
from dnslib import *
127
128
# Create DNS query
129
q = DNSRecord.question("example.com", "A")
130
131
# Send query to server
132
response_packet = q.send("8.8.8.8", 53)
133
134
# Parse response
135
response = DNSRecord.parse(response_packet)
136
137
# Validate transaction ID
138
if q.header.id != response.header.id:
139
raise DNSError('Response transaction ID mismatch')
140
141
# Extract A records
142
for rr in response.rr:
143
if rr.rtype == QTYPE.A:
144
print(f"IP: {rr.rdata}")
145
```
146
147
### DNSSEC Queries
148
149
```python
150
from dnslib import *
151
152
# Create DNSSEC query
153
q = DNSRecord.question("example.com", "A")
154
q.add_ar(EDNS0(flags="do", udp_len=4096))
155
q.header.ad = 1 # Authentic data flag
156
157
# Send query
158
response_packet = q.send("1.1.1.1", 53)
159
response = DNSRecord.parse(response_packet)
160
161
# Check for DNSSEC data
162
has_rrsig = any(rr.rtype == QTYPE.RRSIG for rr in response.rr)
163
authenticated = bool(response.header.ad)
164
165
print(f"DNSSEC signatures: {has_rrsig}")
166
print(f"Authenticated data: {authenticated}")
167
```
168
169
### TCP Fallback Handling
170
171
```python
172
from dnslib import *
173
174
def query_with_fallback(domain, qtype, server="8.8.8.8"):
175
"""Query with automatic TCP fallback for truncated responses."""
176
177
# Create query
178
q = DNSRecord.question(domain, qtype)
179
180
# Try UDP first
181
try:
182
response_packet = q.send(server, 53, tcp=False)
183
response = DNSRecord.parse(response_packet)
184
185
# Check for truncation
186
if response.header.tc:
187
print("Response truncated, retrying with TCP...")
188
response_packet = q.send(server, 53, tcp=True)
189
response = DNSRecord.parse(response_packet)
190
191
return response
192
193
except Exception as e:
194
print(f"Query failed: {e}")
195
return None
196
197
# Use with large responses that might be truncated
198
response = query_with_fallback("google.com", "TXT")
199
if response:
200
print(f"Got {len(response.rr)} records")
201
```
202
203
### Multiple Server Comparison
204
205
```python
206
from dnslib import *
207
208
def compare_servers(domain, qtype, servers):
209
"""Compare responses from multiple DNS servers."""
210
211
results = {}
212
q = DNSRecord.question(domain, qtype)
213
214
for server in servers:
215
try:
216
response_packet = q.send(server, 53)
217
response = DNSRecord.parse(response_packet)
218
219
# Extract answer data
220
answers = []
221
for rr in response.rr:
222
if rr.rtype == QTYPE[qtype]:
223
answers.append(str(rr.rdata))
224
225
results[server] = {
226
'answers': answers,
227
'rcode': RCODE[response.header.rcode],
228
'response_time': None # Would need timing code
229
}
230
231
except Exception as e:
232
results[server] = {'error': str(e)}
233
234
return results
235
236
# Compare multiple servers
237
servers = ["8.8.8.8", "1.1.1.1", "9.9.9.9"]
238
results = compare_servers("example.com", "A", servers)
239
240
for server, result in results.items():
241
if 'error' in result:
242
print(f"{server}: ERROR - {result['error']}")
243
else:
244
print(f"{server}: {result['rcode']} - {', '.join(result['answers'])}")
245
```
246
247
### DiG Output Parsing
248
249
```python
250
from dnslib.digparser import DigParser
251
import subprocess
252
253
def compare_with_dig(domain, qtype, server="8.8.8.8"):
254
"""Compare dnslib response with DiG output."""
255
256
# Query using dnslib
257
q = DNSRecord.question(domain, qtype)
258
dnslib_packet = q.send(server, 53)
259
dnslib_response = DNSRecord.parse(dnslib_packet)
260
261
# Query using DiG
262
dig_cmd = f"dig +qr +noedns +noadflag -p 53 {domain} {qtype} @{server}"
263
dig_output = subprocess.getoutput(dig_cmd)
264
265
# Parse DiG output
266
parser = DigParser()
267
dig_records = parser.parse(dig_output)
268
269
# Compare responses
270
print("dnslib response:")
271
print(dnslib_response)
272
print("\nDigg response:")
273
for record in dig_records:
274
print(record)
275
276
# Example comparison
277
compare_with_dig("example.com", "A")
278
```
279
280
### Bulk DNS Queries
281
282
```python
283
from dnslib import *
284
import concurrent.futures
285
import time
286
287
def query_domain(domain, qtype="A", server="8.8.8.8"):
288
"""Query single domain with timing."""
289
start_time = time.time()
290
291
try:
292
q = DNSRecord.question(domain, qtype)
293
response_packet = q.send(server, 53)
294
response = DNSRecord.parse(response_packet)
295
query_time = (time.time() - start_time) * 1000
296
297
answers = []
298
for rr in response.rr:
299
if rr.rtype == QTYPE[qtype]:
300
answers.append(str(rr.rdata))
301
302
return {
303
'domain': domain,
304
'answers': answers,
305
'time_ms': query_time,
306
'rcode': RCODE[response.header.rcode]
307
}
308
309
except Exception as e:
310
return {'domain': domain, 'error': str(e)}
311
312
# Bulk query multiple domains
313
domains = ["google.com", "github.com", "stackoverflow.com", "example.com"]
314
315
# Concurrent queries
316
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
317
results = list(executor.map(query_domain, domains))
318
319
# Display results
320
for result in results:
321
if 'error' in result:
322
print(f"{result['domain']}: ERROR - {result['error']}")
323
else:
324
print(f"{result['domain']} ({result['time_ms']:.1f}ms): {', '.join(result['answers'])}")
325
```
326
327
### Custom Query Types
328
329
```python
330
from dnslib import *
331
332
def query_all_types(domain, server="8.8.8.8"):
333
"""Query domain for multiple record types."""
334
335
types = ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA"]
336
results = {}
337
338
for qtype in types:
339
try:
340
q = DNSRecord.question(domain, qtype)
341
response_packet = q.send(server, 53)
342
response = DNSRecord.parse(response_packet)
343
344
if response.header.rcode == RCODE.NOERROR and response.rr:
345
records = []
346
for rr in response.rr:
347
if rr.rtype == QTYPE[qtype]:
348
records.append(str(rr.rdata))
349
if records:
350
results[qtype] = records
351
352
except Exception as e:
353
results[qtype] = f"Error: {e}"
354
355
return results
356
357
# Query all common record types
358
all_records = query_all_types("example.com")
359
for record_type, data in all_records.items():
360
print(f"{record_type}: {data}")
361
```