CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dnslib

Simple library to encode/decode DNS wire-format packets

Overview
Eval results
Files

dns-core.mddocs/

DNS Core Functionality

Core DNS packet encoding, decoding, and manipulation functionality that forms the foundation of dnslib. Handles conversion between wire format, Python objects, and Zone/DiG textual representations.

Capabilities

DNS Record Container

Main container class for complete DNS packets with header, questions, answers, authority, and additional sections.

class DNSRecord:
    """
    DNS packet container with header and resource record sections.
    
    Args:
        header (DNSHeader, optional): DNS packet header
        q (DNSQuestion or list, optional): Question section entries
        a (RR or list, optional): Answer section entries  
        auth (RR or list, optional): Authority section entries
        ar (RR or list, optional): Additional section entries
    """
    def __init__(self, header=None, q=None, a=None, auth=None, ar=None): ...
    
    @classmethod
    def parse(cls, packet):
        """
        Parse DNS packet from wire format bytes.
        
        Args:
            packet (bytes): DNS packet in wire format
            
        Returns:
            DNSRecord: Parsed DNS record object
            
        Raises:
            DNSError: If packet is malformed
        """
        
    @classmethod  
    def question(cls, qname, qtype="A", qclass="IN"):
        """
        Create DNS query packet.
        
        Args:
            qname (str): Domain name to query
            qtype (str or int, optional): Query type (default: "A")
            qclass (str or int, optional): Query class (default: "IN")
            
        Returns:
            DNSRecord: DNS query packet
        """
        
    def pack(self):
        """
        Pack DNS record to wire format bytes.
        
        Returns:
            bytes: DNS packet in wire format
        """
        
    def reply(self, ra=1, aa=1):
        """
        Create reply packet based on this query.
        
        Args:
            ra (int, optional): Recursion available flag (default: 1)
            aa (int, optional): Authoritative answer flag (default: 1)
            
        Returns:
            DNSRecord: DNS response packet
        """
        
    def replyZone(self, zone):
        """
        Create reply packet from zone file format string.
        
        Args:
            zone (str): Zone file format resource records
            
        Returns:
            DNSRecord: DNS response packet with zone data
        """
        
    def add_question(self, q):
        """Add question to question section."""
        
    def add_answer(self, *rr):
        """Add resource records to answer section."""
        
    def add_auth(self, *rr):
        """Add resource records to authority section."""
        
    def add_ar(self, *rr):
        """Add resource records to additional section."""
        
    def send(self, dest, port=53, tcp=False, timeout=None, ipv6=False):
        """
        Send DNS query and return response.
        
        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
        """
        
    def format(self, prefix="", sort=False):
        """
        Format DNS record as string with optional prefix and sorting.
        
        Args:
            prefix (str, optional): Prefix for each line
            sort (bool, optional): Sort resource records
            
        Returns:
            str: Formatted DNS record
        """
        
    def toZone(self, prefix=""):
        """
        Convert DNS record to zone file format.
        
        Args:
            prefix (str, optional): Prefix for each line
            
        Returns:
            str: Zone file format string
        """
        
    def short(self):
        """
        Return short string representation of DNS record.
        
        Returns:
            str: Short format string
        """
        
    def diff(self, other):
        """
        Compare DNS records and return differences.
        
        Args:
            other (DNSRecord): Other DNS record to compare
            
        Returns:
            str: Difference description
        """
        
    def truncate(self):
        """
        Truncate DNS record by removing additional and authority sections.
        Sets TC flag in header.
        """
        
    def set_header_qa(self):
        """Update header question/answer counts from sections."""

DNS Header

DNS packet header containing ID, flags, and section counts.

class DNSHeader:
    """
    DNS packet header with ID, flags, and section record counts.
    
    Args:
        id (int, optional): Transaction ID (random if not specified)
        qr (int, optional): Query/Response flag (0=query, 1=response)
        opcode (int, optional): Operation code (0=QUERY, 1=IQUERY, etc.)
        aa (int, optional): Authoritative answer flag
        tc (int, optional): Truncation flag
        rd (int, optional): Recursion desired flag
        ra (int, optional): Recursion available flag
        z (int, optional): Reserved bits (must be 0)
        ad (int, optional): Authentic data flag (DNSSEC)
        cd (int, optional): Checking disabled flag (DNSSEC)
        rcode (int, optional): Response code (0=NOERROR, 3=NXDOMAIN, etc.)
        q (int, optional): Question count
        a (int, optional): Answer count
        auth (int, optional): Authority count
        ar (int, optional): Additional count
    """
    def __init__(self, id=None, **kwargs): ...
    
    def pack(self):
        """
        Pack header to wire format bytes.
        
        Returns:
            bytes: Header in wire format
        """
        
    @classmethod
    def parse(cls, buffer):
        """
        Parse header from wire format buffer.
        
        Args:
            buffer (Buffer): Wire format buffer
            
        Returns:
            DNSHeader: Parsed header object
        """
        
    def toZone(self):
        """
        Convert header to zone file format comment.
        
        Returns:
            str: Zone file format header comment
        """
        
    # Properties for flag access
    @property
    def qr(self):
        """Query/Response flag (0=query, 1=response)."""
        
    @qr.setter
    def qr(self, value):
        """Set Query/Response flag."""
        
    @property
    def opcode(self):
        """Operation code (QUERY, IQUERY, STATUS, etc.)."""
        
    @opcode.setter
    def opcode(self, value):
        """Set operation code."""
        
    @property
    def aa(self):
        """Authoritative Answer flag."""
        
    @aa.setter  
    def aa(self, value):
        """Set Authoritative Answer flag."""
        
    @property
    def tc(self):
        """Truncation flag."""
        
    @tc.setter
    def tc(self, value):
        """Set Truncation flag."""
        
    @property
    def rd(self):
        """Recursion Desired flag."""
        
    @rd.setter
    def rd(self, value):
        """Set Recursion Desired flag."""
        
    @property
    def ra(self):
        """Recursion Available flag."""
        
    @ra.setter
    def ra(self, value):
        """Set Recursion Available flag."""
        
    @property
    def z(self):
        """Reserved bits (must be 0)."""
        
    @z.setter
    def z(self, value):
        """Set reserved bits."""
        
    @property
    def ad(self):
        """Authentic Data flag (DNSSEC)."""
        
    @ad.setter
    def ad(self, value):
        """Set Authentic Data flag."""
        
    @property
    def cd(self):
        """Checking Disabled flag (DNSSEC)."""
        
    @cd.setter
    def cd(self, value):
        """Set Checking Disabled flag."""
        
    @property
    def rcode(self):
        """Response code (NOERROR, NXDOMAIN, etc.)."""
        
    @rcode.setter
    def rcode(self, value):
        """Set response code."""

DNS Question

DNS question section specifying query name, type, and class.

class DNSQuestion:
    """
    DNS question entry specifying query parameters.
    
    Args:
        qname (str or DNSLabel): Domain name to query
        qtype (str or int, optional): Query type (default: "A")
        qclass (str or int, optional): Query class (default: "IN")
    """
    def __init__(self, qname, qtype="A", qclass="IN"): ...
    
    def pack(self):
        """
        Pack question to wire format bytes.
        
        Returns:
            bytes: Question in wire format
        """
        
    @classmethod
    def parse(cls, buffer):
        """
        Parse question from wire format buffer.
        
        Args:
            buffer (Buffer): Wire format buffer
            
        Returns:
            DNSQuestion: Parsed question object
        """

Resource Record

DNS resource record containing name, type, class, TTL, and resource data.

class RR:
    """
    DNS resource record with header and resource data.
    
    Args:
        rname (str or DNSLabel): Resource record name
        rtype (str or int, optional): Record type (default: "A")
        rclass (str or int, optional): Record class (default: "IN")
        ttl (int, optional): Time to live in seconds (default: 0)
        rdata (RD object, optional): Resource data
    """
    def __init__(self, rname, rtype="A", rclass="IN", ttl=0, rdata=None): ...
    
    def pack(self):
        """
        Pack resource record to wire format bytes.
        
        Returns:
            bytes: Resource record in wire format
        """
        
    @classmethod
    def parse(cls, buffer):
        """
        Parse resource record from wire format buffer.
        
        Args:
            buffer (Buffer): Wire format buffer
            
        Returns:
            RR: Parsed resource record object
        """
        
    @classmethod
    def fromZone(cls, zone, origin=None, ttl=None):
        """
        Parse resource records from zone file format string.
        
        Args:
            zone (str): Zone file format string
            origin (str, optional): Default origin domain
            ttl (int, optional): Default TTL value
            
        Returns:
            list[RR]: List of parsed resource records
        """

DNS Labels

DNS name representation with IDNA encoding support and label manipulation.

class DNSLabel:
    """
    DNS label with IDNA encoding support.
    
    Args:
        label (str or list): Domain name string or label components
    """
    def __init__(self, label): ...
    
    def encode(self):
        """
        Encode label to wire format bytes.
        
        Returns:
            bytes: Encoded label
        """
        
    @classmethod
    def decode(cls, buffer):
        """
        Decode label from wire format buffer.
        
        Args:
            buffer (Buffer): Wire format buffer
            
        Returns:
            DNSLabel: Decoded label object
        """
        
    def idna(self):
        """
        Convert label to IDNA format.
        
        Returns:
            DNSLabel: IDNA encoded label
        """
        
    def matchSuffix(self, suffix):
        """
        Check if label ends with suffix (case-insensitive).
        
        Args:
            suffix (str or DNSLabel): Suffix to match
            
        Returns:
            bool: True if label ends with suffix
        """
        
    def stripSuffix(self, suffix):
        """
        Remove suffix from label (case-insensitive).
        
        Args:
            suffix (str or DNSLabel): Suffix to remove
            
        Returns:
            DNSLabel: Label with suffix removed
        """
        
    def matchWildcard(self, pattern):
        """
        Match label against wildcard pattern.
        
        Args:
            pattern (str or DNSLabel): Wildcard pattern (* supported)
            
        Returns:
            bool: True if label matches pattern
        """

EDNS0 Support

Extended DNS (EDNS0) support for larger packets and options.

class EDNS0:
    """
    EDNS0 (Extended DNS) pseudo-record for enhanced DNS functionality.
    
    Args:
        version (int, optional): EDNS version (default: 0)
        flags (str or int, optional): EDNS flags ("do" for DNSSEC OK)
        udp_len (int, optional): UDP payload size (default: 4096)
        rcode_ext (int, optional): Extended response code
        options (list, optional): EDNS options
    """
    def __init__(self, version=0, flags=0, udp_len=4096, rcode_ext=0, options=None): ...
    
    def pack(self):
        """Pack EDNS0 record to wire format."""
        
    @classmethod
    def parse(cls, buffer):
        """Parse EDNS0 record from wire format buffer."""

EDNS Options

EDNS option handling for extended functionality.

class EDNSOption:
    """
    EDNS option for extended DNS functionality.
    
    Args:
        code (int): Option code
        data (bytes): Option data
    """
    def __init__(self, code, data): ...
    
    def pack(self):
        """Pack option to wire format."""
        
    @classmethod
    def parse(cls, buffer):
        """Parse option from wire format buffer."""

Buffer Management

Core buffer functionality for wire format packet handling.

class Buffer:
    """
    Simple data buffer with pack/unpack support for binary data manipulation.
    
    Args:
        data (bytes, optional): Initial buffer data
    """
    def __init__(self, data=b""): ...
    
    def remaining(self):
        """
        Get remaining bytes in buffer.
        
        Returns:
            int: Number of remaining bytes
        """
        
    def get(self, length):
        """
        Get specified number of bytes from buffer.
        
        Args:
            length (int): Number of bytes to read
            
        Returns:
            bytes: Buffer data
        """
        
    def pack(self, fmt, *args):
        """
        Pack data using struct format and append to buffer.
        
        Args:
            fmt (str): Struct format string
            *args: Values to pack
        """
        
    def append(self, data):
        """
        Append data to buffer.
        
        Args:
            data (bytes): Data to append
        """
        
    def unpack(self, fmt):
        """
        Unpack data from buffer using struct format.
        
        Args:
            fmt (str): Struct format string
            
        Returns:
            tuple: Unpacked values
        """
        
    def update(self, ptr, fmt, *args):
        """
        Update buffer at specific position.
        
        Args:
            ptr (int): Buffer position
            fmt (str): Struct format string
            *args: Values to pack
        """
        
    def hex(self):
        """
        Return hex representation of buffer data.
        
        Returns:
            str: Hexadecimal string
        """

DNS Buffer

Extended buffer with DNS name encoding/decoding and compression support.

class DNSBuffer(Buffer):
    """
    DNS-specific buffer with name compression and caching support.
    
    Args:
        data (bytes, optional): Initial buffer data
    """
    def __init__(self, data=b""): ...
    
    def decode_name(self, last=-1):
        """
        Decode DNS name from buffer with compression support.
        
        Args:
            last (int, optional): Last position for compression
            
        Returns:
            DNSLabel: Decoded DNS name
        """
        
    def encode_name(self, name):
        """
        Encode DNS name to buffer with compression.
        
        Args:
            name (str or DNSLabel): DNS name to encode
        """
        
    def encode_name_nocompress(self, name):
        """
        Encode DNS name without compression.
        
        Args:
            name (str or DNSLabel): DNS name to encode
        """

Bidirectional Mapping

Bidirectional mapping between numeric codes and text labels.

class Bimap:
    """
    Bidirectional mapping for DNS constants (e.g., QTYPE, CLASS, RCODE).
    
    Args:
        name (str): Mapping name for error messages
        forward (dict): Forward mapping (code -> text)
        error (Exception, optional): Exception class for errors
    """
    def __init__(self, name, forward, error=AttributeError): ...
    
    def get(self, key, default=None):
        """
        Get value with default fallback.
        
        Args:
            key: Lookup key
            default: Default value if key not found
            
        Returns:
            Value or default
        """
        
    def __getitem__(self, key):
        """
        Forward lookup: code -> text.
        
        Args:
            key: Numeric code
            
        Returns:
            str: Text label
        """
        
    def __getattr__(self, key):
        """
        Reverse lookup: text -> code.
        
        Args:
            key (str): Text label
            
        Returns:
            int: Numeric code
        """

Zone File Parser

Parser for DNS zone file format with support for $TTL and $ORIGIN directives.

class ZoneParser:
    """
    Parser for DNS zone file format strings.
    
    Args:
        zone (str): Zone file format string
        origin (str, optional): Default origin domain
        ttl (int, optional): Default TTL value
    """
    def __init__(self, zone, origin="", ttl=0): ...
    
    def parse(self):
        """
        Parse zone file and return resource records.
        
        Returns:
            list[RR]: List of parsed resource records
        """
        
    def expect(self, token):
        """
        Expect specific token in zone file.
        
        Args:
            token (str): Expected token
            
        Raises:
            Exception: If token not found
        """
        
    def parse_label(self, label):
        """
        Parse DNS label from zone file.
        
        Args:
            label (str): Label string
            
        Returns:
            DNSLabel: Parsed label
        """
        
    def parse_rr(self, rr_line):
        """
        Parse resource record line from zone file.
        
        Args:
            rr_line (str): Resource record line
            
        Returns:
            RR: Parsed resource record
        """

Utility Functions

Core utility functions for DNS label and time handling.

def label(label, origin=None):
    """
    Create DNS label with optional origin.
    
    Args:
        label (str): Label string
        origin (str or DNSLabel, optional): Origin domain
        
    Returns:
        DNSLabel: DNS label object
    """

def parse_time(s):
    """
    Parse time value for DNS records.
    
    Args:
        s (str): Time string (e.g., "1h", "30m", "3600")
        
    Returns:
        int: Time value in seconds
    """

def decode_type_bitmap(type_bitmap):
    """
    Decode NSEC type bitmap.
    
    Args:
        type_bitmap (bytes): Type bitmap data
        
    Returns:
        list[int]: List of record type numbers
    """

def encode_type_bitmap(rrlist):
    """
    Encode NSEC type bitmap.
    
    Args:
        rrlist (list[int]): List of record type numbers
        
    Returns:
        bytes: Type bitmap data
    """

def unknown_qtype(name, key, forward):
    """
    Handle unknown query types for dynamic mapping.
    
    Args:
        name (str): Mapping name
        key: Lookup key
        forward (bool): True for forward lookup
        
    Returns:
        Mapped value or TYPE### format
    """

Usage Examples

Basic Packet Parsing

import binascii
from dnslib import *

# Parse DNS response packet
packet_hex = 'd5ad818000010005000000000377777706676f6f676c6503636f6d0000010001...'
packet = binascii.unhexlify(packet_hex.encode())
d = DNSRecord.parse(packet)

print(f"Header: {d.header}")
print(f"Question: {d.q}")
for rr in d.rr:
    print(f"Answer: {rr}")

Creating DNS Queries

from dnslib import *

# Simple A query
q = DNSRecord.question("example.com")
print(q)

# MX query with specific header flags
q = DNSRecord.question("example.com", "MX")
q.header.rd = 1  # Recursion desired
print(q)

# DNSSEC query with EDNS0
q = DNSRecord.question("example.com", "A")
q.add_ar(EDNS0(flags="do", udp_len=4096))
q.header.ad = 1  # Authentic data
print(q)

Creating DNS Responses

from dnslib import *

# Create response manually
response = DNSRecord(
    DNSHeader(qr=1, aa=1, ra=1),
    q=DNSQuestion("example.com"),
    a=RR("example.com", rdata=A("1.2.3.4"), ttl=300)
)

# Create response from query
q = DNSRecord.question("example.com")
a = q.reply()
a.add_answer(*RR.fromZone("example.com 300 IN A 1.2.3.4"))
a.add_answer(*RR.fromZone("example.com 300 IN AAAA 2001:db8::1"))

# Verify round-trip
packed = a.pack()
parsed = DNSRecord.parse(packed)
assert str(parsed) == str(a)

Zone File Processing

from dnslib import *

# Parse zone file format
zone_data = """
$TTL 300
$ORIGIN example.com.

@       IN      MX      10  mail.example.com.
www     IN      A       1.2.3.4
        IN      TXT     "Some Text"
mail    IN      CNAME   www.example.com.
"""

# Create records from zone data
records = RR.fromZone(zone_data.strip())
for rr in records:
    print(rr)

# Create response with zone data
q = DNSRecord.question("example.com", "ANY")
a = q.replyZone(zone_data.strip())
print(a)

Install with Tessl CLI

npx tessl i tessl/pypi-dnslib

docs

dns-client.md

dns-core.md

dns-records.md

dns-resolvers.md

dns-server.md

dns-utils.md

index.md

tile.json