Simple library to encode/decode DNS wire-format packets
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.
DNS queries can be performed programmatically using the core DNSRecord.send() method and related functionality.
# Core query functionality (from dnslib.dns)
def send(self, dest, port=53, tcp=False, timeout=None, ipv6=False):
"""
Send DNS query and return response packet.
Available on DNSRecord instances.
Args:
dest (str): Destination server address
port (int, optional): Destination port (default: 53)
tcp (bool, optional): Use TCP instead of UDP (default: False)
timeout (int, optional): Query timeout in seconds (default: None)
ipv6 (bool, optional): Use IPv6 (default: False)
Returns:
bytes: DNS response packet in wire format
"""Integration with DiG output parsing for response comparison and validation.
# From dnslib.digparser
class DigParser:
"""
Parser for DiG textual output format.
Enables comparison between dnslib and DiG responses.
"""
def parse(self, dig_output):
"""
Parse DiG output into DNSRecord objects.
Args:
dig_output (str): DiG command output text
Returns:
list[DNSRecord]: Parsed DNS records
"""The dnslib.client module provides a comprehensive DiG-like command-line interface for DNS queries.
# Query A record for domain
python -m dnslib.client example.com
# Query specific record type
python -m dnslib.client example.com MX
# Query specific server
python -m dnslib.client --server 8.8.8.8 example.com
# Use TCP protocol
python -m dnslib.client --tcp example.com
# Enable DNSSEC
python -m dnslib.client --dnssec example.com
# Show query packet
python -m dnslib.client --query example.com
# Show packet in hex
python -m dnslib.client --hex example.com
# Short output (rdata only)
python -m dnslib.client --short example.com# Compare responses from different servers
python -m dnslib.client --diff 1.1.1.1 --server 8.8.8.8 example.com
# Compare with DiG output
python -m dnslib.client --dig example.com
# Combine comparison and DiG
python -m dnslib.client --diff 1.1.1.1 --dig example.com
# Disable TCP fallback for truncated responses
python -m dnslib.client --noretry example.com
# Debug mode - drop into CLI after request
python -m dnslib.client --debug example.compython -m dnslib.client [OPTIONS] <domain> [<type>]
Options:
--server, -s ADDRESS:PORT Server address:port (default: 8.8.8.8:53)
--query Show query packet
--hex Dump packet in hex format
--tcp Use TCP instead of UDP
--noretry Don't retry with TCP if truncated
--diff ADDRESS:PORT Compare with alternate nameserver
--dig Compare result with DiG
--short Short output - rdata only
--dnssec Set DNSSEC (DO/AD) flags
--debug Drop into CLI after requestfrom dnslib import *
# Create DNS query
q = DNSRecord.question("example.com", "A")
# Send query to server
response_packet = q.send("8.8.8.8", 53)
# Parse response
response = DNSRecord.parse(response_packet)
# Validate transaction ID
if q.header.id != response.header.id:
raise DNSError('Response transaction ID mismatch')
# Extract A records
for rr in response.rr:
if rr.rtype == QTYPE.A:
print(f"IP: {rr.rdata}")from dnslib import *
# Create DNSSEC query
q = DNSRecord.question("example.com", "A")
q.add_ar(EDNS0(flags="do", udp_len=4096))
q.header.ad = 1 # Authentic data flag
# Send query
response_packet = q.send("1.1.1.1", 53)
response = DNSRecord.parse(response_packet)
# Check for DNSSEC data
has_rrsig = any(rr.rtype == QTYPE.RRSIG for rr in response.rr)
authenticated = bool(response.header.ad)
print(f"DNSSEC signatures: {has_rrsig}")
print(f"Authenticated data: {authenticated}")from dnslib import *
def query_with_fallback(domain, qtype, server="8.8.8.8"):
"""Query with automatic TCP fallback for truncated responses."""
# Create query
q = DNSRecord.question(domain, qtype)
# Try UDP first
try:
response_packet = q.send(server, 53, tcp=False)
response = DNSRecord.parse(response_packet)
# Check for truncation
if response.header.tc:
print("Response truncated, retrying with TCP...")
response_packet = q.send(server, 53, tcp=True)
response = DNSRecord.parse(response_packet)
return response
except Exception as e:
print(f"Query failed: {e}")
return None
# Use with large responses that might be truncated
response = query_with_fallback("google.com", "TXT")
if response:
print(f"Got {len(response.rr)} records")from dnslib import *
def compare_servers(domain, qtype, servers):
"""Compare responses from multiple DNS servers."""
results = {}
q = DNSRecord.question(domain, qtype)
for server in servers:
try:
response_packet = q.send(server, 53)
response = DNSRecord.parse(response_packet)
# Extract answer data
answers = []
for rr in response.rr:
if rr.rtype == QTYPE[qtype]:
answers.append(str(rr.rdata))
results[server] = {
'answers': answers,
'rcode': RCODE[response.header.rcode],
'response_time': None # Would need timing code
}
except Exception as e:
results[server] = {'error': str(e)}
return results
# Compare multiple servers
servers = ["8.8.8.8", "1.1.1.1", "9.9.9.9"]
results = compare_servers("example.com", "A", servers)
for server, result in results.items():
if 'error' in result:
print(f"{server}: ERROR - {result['error']}")
else:
print(f"{server}: {result['rcode']} - {', '.join(result['answers'])}")from dnslib.digparser import DigParser
import subprocess
def compare_with_dig(domain, qtype, server="8.8.8.8"):
"""Compare dnslib response with DiG output."""
# Query using dnslib
q = DNSRecord.question(domain, qtype)
dnslib_packet = q.send(server, 53)
dnslib_response = DNSRecord.parse(dnslib_packet)
# Query using DiG
dig_cmd = f"dig +qr +noedns +noadflag -p 53 {domain} {qtype} @{server}"
dig_output = subprocess.getoutput(dig_cmd)
# Parse DiG output
parser = DigParser()
dig_records = parser.parse(dig_output)
# Compare responses
print("dnslib response:")
print(dnslib_response)
print("\nDigg response:")
for record in dig_records:
print(record)
# Example comparison
compare_with_dig("example.com", "A")from dnslib import *
import concurrent.futures
import time
def query_domain(domain, qtype="A", server="8.8.8.8"):
"""Query single domain with timing."""
start_time = time.time()
try:
q = DNSRecord.question(domain, qtype)
response_packet = q.send(server, 53)
response = DNSRecord.parse(response_packet)
query_time = (time.time() - start_time) * 1000
answers = []
for rr in response.rr:
if rr.rtype == QTYPE[qtype]:
answers.append(str(rr.rdata))
return {
'domain': domain,
'answers': answers,
'time_ms': query_time,
'rcode': RCODE[response.header.rcode]
}
except Exception as e:
return {'domain': domain, 'error': str(e)}
# Bulk query multiple domains
domains = ["google.com", "github.com", "stackoverflow.com", "example.com"]
# Concurrent queries
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(query_domain, domains))
# Display results
for result in results:
if 'error' in result:
print(f"{result['domain']}: ERROR - {result['error']}")
else:
print(f"{result['domain']} ({result['time_ms']:.1f}ms): {', '.join(result['answers'])}")from dnslib import *
def query_all_types(domain, server="8.8.8.8"):
"""Query domain for multiple record types."""
types = ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA"]
results = {}
for qtype in types:
try:
q = DNSRecord.question(domain, qtype)
response_packet = q.send(server, 53)
response = DNSRecord.parse(response_packet)
if response.header.rcode == RCODE.NOERROR and response.rr:
records = []
for rr in response.rr:
if rr.rtype == QTYPE[qtype]:
records.append(str(rr.rdata))
if records:
results[qtype] = records
except Exception as e:
results[qtype] = f"Error: {e}"
return results
# Query all common record types
all_records = query_all_types("example.com")
for record_type, data in all_records.items():
print(f"{record_type}: {data}")Install with Tessl CLI
npx tessl i tessl/pypi-dnslib@0.9.2