CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dnslib

Simple library to encode/decode DNS wire-format packets

Overview
Eval results
Files

dns-client.mddocs/

DNS Client Utilities

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.

Capabilities

Programmatic DNS Queries

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
    """

DiG Output Parser

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
        """

Command Line Interface

The dnslib.client module provides a comprehensive DiG-like command-line interface for DNS queries.

Basic Usage

# 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

Advanced Options

# 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.com

Command Line Parameters

python -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 request

Usage Examples

Basic Programmatic Queries

from 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}")

DNSSEC Queries

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}")

TCP Fallback Handling

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")

Multiple Server Comparison

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'])}")

DiG Output Parsing

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")

Bulk DNS Queries

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'])}")

Custom Query Types

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

docs

dns-client.md

dns-core.md

dns-records.md

dns-resolvers.md

dns-server.md

dns-utils.md

index.md

tile.json