Parsing DNS Zone Files with Python: A Legacy Tool from Blockstack's Decentralized Web
Hook
Before Blockstack became Stacks and pivoted to Bitcoin smart contracts, it stored user profiles in DNS zone files on the blockchain—a clever hack that made decentralized identity look exactly like traditional DNS.
Context
DNS zone files have been the authoritative format for describing internet naming infrastructure since RFC 1035 was published in 1987. These text-based files use a specific syntax to declare resource records that map domain names to IP addresses, mail servers, and other resources. While perfectly functional for DNS administrators, zone files are notoriously difficult for applications to work with programmatically. Their whitespace-sensitive format, cryptic record types, and directive syntax ($ORIGIN, $TTL) make them challenging to parse and generate reliably.
Blockstack faced this problem when building their decentralized naming system. They needed to store structured profile data (names, avatars, social links) in a format that was both human-readable and compatible with existing DNS tooling. Their solution was to leverage DNS zone files as a data serialization format, storing user profiles as URI records and other DNS record types. But to make this practical, they needed a reliable way to convert between zone file text and structured data that application code could manipulate. That's where zone-file-py came in—a Python library that provides bidirectional conversion between RFC 1035 zone files and Python dictionaries.
Technical Insight
The core architecture of zone-file-py centers on two primary functions: parse_zone_file() for converting zone file text into Python dictionaries, and make_zone_file() for the reverse operation. The library handles the complexity of zone file syntax so developers can work with clean, structured data.
Here's a practical example of parsing a zone file containing profile information:
from zone_file import parse_zone_file
zone_file_text = """
$ORIGIN example.com.
$TTL 3600
@ IN A 192.168.1.1
www IN CNAME example.com.
_http._tcp IN URI 10 1 "https://example.com/profile.json"
"""
records = parse_zone_file(zone_file_text)
print(records)
# Output: {
# '$origin': 'EXAMPLE.COM.',
# '$ttl': 3600,
# 'a': [{'name': '@', 'ip': '192.168.1.1'}],
# 'cname': [{'name': 'WWW', 'alias': 'EXAMPLE.COM.'}],
# 'uri': [{'name': '_HTTP._TCP', 'priority': 10, 'weight': 1, 'target': 'https://example.com/profile.json'}]
# }
The parser normalizes record names to uppercase and organizes records by type into separate arrays. This makes it trivial to extract specific record types or iterate through all records of a given kind. The design choice to normalize to uppercase is interesting—while it loses the original casing, DNS is case-insensitive anyway, and the normalization simplifies string comparisons in application code.
Generating zone files from structured data is equally straightforward:
from zone_file import make_zone_file
record_dict = {
'$origin': 'MYAPP.ID',
'$ttl': 3600,
'uri': [
{
'name': '_http._tcp',
'priority': 10,
'weight': 1,
'target': 'https://gaia.blockstack.org/hub/1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs/profile.json'
}
],
'txt': [
{
'name': 'pubkey',
'txt': 'pubkey:data:0256f3b42b2d687c6f7ccdcf7e88e96e89ac89b43a2d'}
]
}
zone_file = make_zone_file(record_dict)
print(zone_file)
# Output:
# $ORIGIN MYAPP.ID
# $TTL 3600
# _http._tcp URI 10 1 "https://gaia.blockstack.org/hub/1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs/profile.json"
# pubkey TXT "pubkey:data:0256f3b42b2d687c6f7ccdcf7e88e96e89ac89b43a2d"
This bidirectional conversion capability was essential for Blockstack's architecture. The system would parse zone files retrieved from the blockchain, modify profile data in dictionary form, then serialize back to zone file format for storage. The abstraction layer eliminated the need for string manipulation and regex parsing scattered throughout the codebase.
The library supports the most common DNS record types: A (IPv4 addresses), AAAA (IPv6 addresses), CNAME (canonical names), MX (mail exchange), PTR (pointer records), TXT (text records), SRV (service records), and notably URI records. URI record support was crucial for Blockstack's use case—these records allow storing arbitrary URLs in DNS, which Blockstack used to point to user profile data stored in decentralized storage systems like Gaia.
One architectural decision worth noting is how the library handles zone file directives. The $ORIGIN and $TTL directives become top-level keys in the resulting dictionary ('$origin' and '$ttl'), making them easy to access and modify separately from the actual resource records. This clean separation means you can change the default TTL for all records by modifying a single dictionary key, then regenerate the entire zone file with the updated value.
Gotcha
The archived status of zone-file-py is its biggest limitation. The repository was archived when Blockstack rebranded to Stacks and shifted away from using DNS zone files as their primary data format. This means no bug fixes, no security patches, and no support for newer Python versions. If you encounter issues with Python 3.10+ or discover parsing bugs with edge-case zone file syntax, you're on your own—or you'll need to fork and maintain it yourself.
The library's DNS record type support is also limited compared to comprehensive DNS libraries. It handles the common types well, but if you need DNSSEC records (RRSIG, DNSKEY, DS), CAA records for certificate authority authorization, or any of the more specialized record types defined in the various DNS RFC extensions, you'll find them missing. The parser simply ignores record types it doesn't recognize, which could lead to silent data loss if you're working with zone files from production DNS infrastructure. Additionally, the case normalization to uppercase means you can't preserve the original formatting of zone files—if you're using this library as part of a DNS management system where preserving original formatting matters (for version control diffs, for example), this normalization will cause noise in your change tracking.
Verdict
Use if: You're maintaining legacy Blockstack applications that rely on zone file storage, need a lightweight way to generate simple zone files for testing or mock DNS setups, or want to understand how decentralized naming systems have used DNS structures. The library works fine for basic A, CNAME, TXT, and URI records, and the JSON abstraction is genuinely cleaner than regex-parsing zone files yourself. Skip if: You need production-grade DNS infrastructure tools, require comprehensive record type support, want active maintenance and security updates, or are starting any new project—in those cases, reach for dnspython, which is actively maintained, battle-tested, and supports the full DNS specification. Given that this repository is archived and Stacks has moved on from this architecture, treat zone-file-py as a historical artifact that's useful for understanding blockchain naming systems, but not a foundation for new development.