gcnspack

Simple DNS resolver and file-over-DNS transfer toolkit.

Features
- DNS Resolver - Simple, batteries-included DNS resolution with sensible defaults (Cloudflare, Google, Quad9 fallbacks)
- File-over-DNS - Encode arbitrary files into DNS TXT records and reconstruct them from DNS queries
- CLI - Command-line interface for resolving DNS and managing file transfers
- Protocol - Chunked base32/base64 encoding scheme with SHA-256 integrity verification
Installation
pip install gcnspack
Or with uv:
uv add gcnspack
Quick Start
DNS Resolution
from gcnspack import resolve, resolve_all, create_resolver
ips = resolve("example.com")
mx = resolve("example.com", "MX")
txt = resolve("example.com", "TXT")
all_records = resolve_all("example.com")
r = create_resolver(nameservers=["1.1.1.1"], timeout=10.0)
result = resolve("example.com", "A", resolver=r)
File-over-DNS Transfer
Encoding a File
from gcnspack import encode_file, generate_zone_records
with open("payload.bin", "rb") as f:
data = f.read()
records = generate_zone_records(data, "files.example.com")
for record in records:
print(record)
Output:
0.files.example.com IN TXT "GAYTEMZUGU3DOOB..."
1.files.example.com IN TXT "GAYTEMZUGU3DOOC..."
_meta.files.example.com IN TXT "ON2HE2LOM4QHO..."
Fetching a File from DNS
from gcnspack import save_txt_file
save_txt_file("files.example.com", "output.bin")
Protocol
Files are transferred over DNS using this encoding scheme:
+-------------------+
| Original File |
+-------------------+
|
Split into chunks
(150 bytes each)
|
+-------+-------+
| | |
v v v
Chunk 0 Chunk 1 Chunk N
| | |
Base64 encode each chunk
| | |
Add header: "idx:total:base64data"
| | |
Base32 encode entire string
| | |
v v v
+-------------------------------------------+
| DNS TXT Records |
| 0.sub.example.com TXT "<base32>" |
| 1.sub.example.com TXT "<base32>" |
| N.sub.example.com TXT "<base32>" |
| _meta.sub.example.com TXT "<meta_b32>" |
+-------------------------------------------+
Metadata record (_meta.<subdomain>): Contains sha256:<hash>:chunks:<count>, base32-encoded.
Chunk records (<n>.<subdomain>): Each contains <index>:<total>:<base64_data>, base32-encoded.
Reconstruction:
- Fetch
_meta.<subdomain> to get chunk count and expected SHA-256 hash
- Fetch chunks
0 through N-1 from <n>.<subdomain>
- Decode each chunk (base32 -> parse header -> base64 decode payload)
- Sort by index, concatenate
- Verify SHA-256 hash matches
CLI Usage
Resolve DNS Records
gcnspack resolve example.com
gcnspack resolve example.com -t MX
gcnspack resolve example.com -n 1.1.1.1

Encode a File for DNS
gcnspack encode payload.bin -s files.example.com
gcnspack encode payload.bin -s files.example.com -o records.zone

Fetch a File from DNS
gcnspack fetch files.example.com -o output.bin

Development
git clone https://github.com/geekennedy/gcnspack.git
cd gcnspack
uv sync
uv run pytest -q -m "not integration"
uv run pytest -q -m integration
uv run pytest -q
uv run ruff check src/ tests/
uv run ruff format src/ tests/
License
MIT License - see LICENSE for details.
Author
geekennedy - gkennedy@gcloudns.net