DNS toolkit for Python supporting almost all record types with high-level and low-level DNS operations
85
Dynamic DNS update functionality for creating and sending DNS UPDATE messages. Provides complete support for adding, deleting, and replacing resource records with optional TSIG authentication.
Create DNS UPDATE messages for dynamic zone modifications.
class Update:
"""
A DNS dynamic update message.
Represents a DNS UPDATE message that can add, delete, or replace
resource records in a DNS zone. Supports prerequisites and
TSIG authentication.
Attributes:
zone (dns.name.Name): Zone being updated
rdclass (int): Zone class
keyring (dict): TSIG keyring for authentication
keyname (dns.name.Name): TSIG key name
keyalgorithm (dns.name.Name): TSIG algorithm
"""
def __init__(self, zone, rdclass='IN', keyring=None, keyname=None,
keyalgorithm='HMAC-MD5.SIG-ALG.REG.INT'):
"""
Initialize DNS update message.
Args:
zone (str or dns.name.Name): Zone name to update
rdclass (str or int): Zone class (default 'IN')
keyring (dict): TSIG keyring for authentication
keyname (str or dns.name.Name): TSIG key name
keyalgorithm (str): TSIG algorithm name
"""
def __str__(self):
"""Return string representation of update."""
def __repr__(self):
"""Return detailed string representation."""Add resource records to the zone.
def add(name, *args):
"""
Add resource records to the zone.
Args:
name (str or dns.name.Name): Record name
*args: Variable arguments specifying record data
Can be (ttl, rdtype, rdata...) or (rdtype, rdata...)
Examples:
update.add('www.example.com', 300, 'A', '192.0.2.1')
update.add('mail', 'MX', 10, 'mail.example.com.')
update.add('_sip._tcp', 'SRV', 10, 20, 5060, 'sip.example.com.')
"""
def add_rrset(name, rrset):
"""
Add an entire RRset to the zone.
Args:
name (str or dns.name.Name): Record name
rrset (dns.rrset.RRset): RRset to add
"""
def add_rdataset(name, rdataset):
"""
Add an rdataset to the zone.
Args:
name (str or dns.name.Name): Record name
rdataset (dns.rdataset.Rdataset): Rdataset to add
"""Delete resource records from the zone.
def delete(name, *args):
"""
Delete resource records from the zone.
Args:
name (str or dns.name.Name): Record name
*args: Optional arguments specifying what to delete
() - delete all records at name
(rdtype) - delete all records of specified type
(rdtype, rdata...) - delete specific record
Examples:
update.delete('old.example.com') # Delete all records
update.delete('www.example.com', 'A') # Delete all A records
update.delete('www.example.com', 'A', '192.0.2.1') # Delete specific A record
"""
def delete_rrset(name, rdtype, covers='NONE'):
"""
Delete an entire RRset from the zone.
Args:
name (str or dns.name.Name): Record name
rdtype (str or int): Record type to delete
covers (str or int): Covered type for RRSIG records
"""
def delete_rdataset(name, rdataset):
"""
Delete specific rdataset from the zone.
Args:
name (str or dns.name.Name): Record name
rdataset (dns.rdataset.Rdataset): Rdataset to delete
"""Replace existing resource records in the zone.
def replace(name, *args):
"""
Replace resource records in the zone.
Deletes all existing records of the specified type and adds new ones.
Args:
name (str or dns.name.Name): Record name
*args: Arguments specifying replacement records
Format: (ttl, rdtype, rdata...) or (rdtype, rdata...)
Examples:
update.replace('www.example.com', 300, 'A', '192.0.2.10')
update.replace('mail', 'MX', 10, 'mail1.example.com.', 20, 'mail2.example.com.')
"""
def replace_rrset(name, rrset):
"""
Replace an RRset in the zone.
Args:
name (str or dns.name.Name): Record name
rrset (dns.rrset.RRset): Replacement RRset
"""
def replace_rdataset(name, rdataset):
"""
Replace an rdataset in the zone.
Args:
name (str or dns.name.Name): Record name
rdataset (dns.rdataset.Rdataset): Replacement rdataset
"""Set prerequisites that must be satisfied for the update to succeed.
def present(name, *args):
"""
Specify that records must be present for update to succeed.
Args:
name (str or dns.name.Name): Record name
*args: Optional arguments specifying what must be present
() - name must exist (any record type)
(rdtype) - name must have records of specified type
(rdtype, rdata...) - specific record must exist
Examples:
update.present('www.example.com') # Name must exist
update.present('www.example.com', 'A') # Must have A records
update.present('www.example.com', 'A', '192.0.2.1') # Specific A record must exist
"""
def absent(name, *args):
"""
Specify that records must be absent for update to succeed.
Args:
name (str or dns.name.Name): Record name
*args: Optional arguments specifying what must be absent
() - name must not exist (no records of any type)
(rdtype) - name must not have records of specified type
(rdtype, rdata...) - specific record must not exist
Examples:
update.absent('new.example.com') # Name must not exist
update.absent('www.example.com', 'AAAA') # Must not have AAAA records
update.absent('www.example.com', 'A', '192.0.2.99') # Specific A record must not exist
"""Send update messages to nameservers.
def to_wire(self):
"""
Convert update message to wire format.
Returns:
bytes: Wire format update message
"""
def __call__(self, nameserver, timeout=None, port=53, af=None, source=None, source_port=0):
"""
Send the update to a nameserver (callable interface).
Args:
nameserver (str): Nameserver IP address
timeout (float): Query timeout
port (int): Destination port (default 53)
af (int): Address family
source (str): Source IP address
source_port (int): Source port
Returns:
dns.message.Message: Response message
"""import dns.update
import dns.query
import dns.rdatatype
# Create update message
zone = 'example.com.'
update = dns.update.Update(zone)
# Add records
update.add('www.example.com.', 300, 'A', '192.0.2.10')
update.add('www.example.com.', 300, 'AAAA', '2001:db8::10')
update.add('mail.example.com.', 300, 'A', '192.0.2.20')
# Add MX record
update.add('example.com.', 300, 'MX', 10, 'mail.example.com.')
# Send update
nameserver = '192.0.2.1' # Authoritative nameserver
response = dns.query.tcp(update, nameserver)
if response.rcode() == dns.rcode.NOERROR:
print("Update successful")
else:
print(f"Update failed: {dns.rcode.to_text(response.rcode())}")import dns.update
update = dns.update.Update('example.com.')
# Delete specific record
update.delete('old.example.com.', 'A', '192.0.2.99')
# Delete all A records at name
update.delete('www.example.com.', 'A')
# Delete all records at name
update.delete('obsolete.example.com.')
# Replace all A records
update.replace('www.example.com.', 300, 'A', '192.0.2.100')
# Replace MX records
update.replace('example.com.', 300, 'MX', 10, 'mail1.example.com.', 20, 'mail2.example.com.')
# Send update
response = dns.query.tcp(update, '192.0.2.1')import dns.update
update = dns.update.Update('example.com.')
# Only proceed if www.example.com has specific A record
update.present('www.example.com.', 'A', '192.0.2.1')
# Replace it with new address
update.replace('www.example.com.', 300, 'A', '192.0.2.10')
# Only proceed if new.example.com doesn't exist
update.absent('new.example.com.')
# Add new record
update.add('new.example.com.', 300, 'A', '192.0.2.50')
# Send conditional update
response = dns.query.tcp(update, '192.0.2.1')import dns.update
import dns.tsigkeyring
import dns.query
# Create TSIG keyring
keyring = dns.tsigkeyring.from_text({
'update-key.example.com.': 'base64-encoded-key-data'
})
# Create authenticated update
update = dns.update.Update(
'example.com.',
keyring=keyring,
keyname='update-key.example.com.'
)
# Add records
update.add('secure.example.com.', 300, 'A', '192.0.2.100')
update.add('secure.example.com.', 300, 'TXT', 'Updated via authenticated TSIG')
# Send authenticated update
response = dns.query.tcp(update, '192.0.2.1')
if response.rcode() == dns.rcode.NOERROR:
print("Authenticated update successful")
# Verify TSIG authentication
if response.had_tsig():
if response.tsig_error() == 0:
print("TSIG verification successful")
else:
print(f"TSIG error: {response.tsig_error()}")
else:
print(f"Update failed: {dns.rcode.to_text(response.rcode())}")import dns.update
update = dns.update.Update('example.com.')
# Add SRV records for SIP service
update.add('_sip._tcp.example.com.', 300, 'SRV', 10, 20, 5060, 'sip1.example.com.')
update.add('_sip._tcp.example.com.', 300, 'SRV', 10, 30, 5060, 'sip2.example.com.')
update.add('_sip._tcp.example.com.', 300, 'SRV', 20, 10, 5060, 'sip3.example.com.')
# Add corresponding A records
update.add('sip1.example.com.', 300, 'A', '192.0.2.11')
update.add('sip2.example.com.', 300, 'A', '192.0.2.12')
update.add('sip3.example.com.', 300, 'A', '192.0.2.13')
# Add NAPTR record for service discovery
update.add('example.com.', 300, 'NAPTR', 100, 10, 'u', 'E2U+sip',
'!^.*$!sip:info@example.com!', '.')
# Send update
response = dns.query.tcp(update, '192.0.2.1')import dns.update
import dns.rrset
import dns.rdata
import dns.rdatatype
import dns.rdataclass
update = dns.update.Update('example.com.')
# Create RRsets for batch operations
www_rrset = dns.rrset.RRset(dns.name.from_text('www.example.com.'),
dns.rdataclass.IN, dns.rdatatype.A)
www_rrset.add(dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'), ttl=300)
www_rrset.add(dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.2'), ttl=300)
# Add entire RRset
update.add_rrset('www.example.com.', www_rrset)
# Create and add MX RRset
mx_rrset = dns.rrset.from_text('example.com.', 300, 'IN', 'MX',
'10 mail1.example.com.', '20 mail2.example.com.')
update.add_rrset('example.com.', mx_rrset)
# Send batch update
response = dns.query.tcp(update, '192.0.2.1')import dns.update
import dns.query
import dns.rcode
import dns.exception
try:
update = dns.update.Update('example.com.')
update.add('test.example.com.', 300, 'A', '192.0.2.50')
response = dns.query.tcp(update, '192.0.2.1', timeout=10)
rcode = response.rcode()
if rcode == dns.rcode.NOERROR:
print("Update completed successfully")
elif rcode == dns.rcode.REFUSED:
print("Update refused - check authorization")
elif rcode == dns.rcode.NOTAUTH:
print("Server not authoritative for zone")
elif rcode == dns.rcode.YXDOMAIN:
print("Prerequisites failed - domain exists when it shouldn't")
elif rcode == dns.rcode.NXDOMAIN:
print("Prerequisites failed - domain doesn't exist when it should")
elif rcode == dns.rcode.YXRRSET:
print("Prerequisites failed - RRset exists when it shouldn't")
elif rcode == dns.rcode.NXRRSET:
print("Prerequisites failed - RRset doesn't exist when it should")
else:
print(f"Update failed with rcode: {dns.rcode.to_text(rcode)}")
except dns.exception.Timeout:
print("Update timed out")
except dns.tsig.BadSignature:
print("TSIG signature verification failed")
except dns.exception.DNSException as e:
print(f"DNS error: {e}")# Standard response codes relevant to updates
NOERROR = 0 # No error - update successful
FORMERR = 1 # Format error in update message
SERVFAIL = 2 # Server failure
NXDOMAIN = 3 # Name does not exist (prerequisite)
NOTIMP = 4 # Not implemented
REFUSED = 5 # Update refused
YXDOMAIN = 6 # Name exists when it should not (prerequisite)
YXRRSET = 7 # RRset exists when it should not (prerequisite)
NXRRSET = 8 # RRset does not exist when it should (prerequisite)
NOTAUTH = 9 # Server not authoritative for zone
NOTZONE = 10 # Name not contained in zoneDNS Updates work seamlessly with other dnspython modules:
dns.tsig and dns.tsigkeyring for secure updatesdns.rdata and dns.rrset for complex record structuresdns.name for proper name formatting and validationdns.query for sending updates via UDP or TCPdns.message for processing update responsesInstall with Tessl CLI
npx tessl i tessl/pypi-dnspythondocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10