RFC Compliance Checker for DNS Protocol Implementations
Project description
rfc-lint
RFC Compliance Checker for DNS Protocol Implementations
Feed it a pcap. It checks every DNS packet against MUST/SHOULD/MAY requirements from RFC 1035 and RFC 6891, flags violations, and produces structured compliance reports.
Quick Start
# Install
pip install -e ".[dev]"
# Lint a pcap
rfc-lint capture.pcap
# Filter by RFC, output JSON
rfc-lint capture.pcap --rfc 1035 --format json -o report.json
What It Checks
RFC 1035 — DNS Message Format (17 rules)
| Rule ID | Section | Level | What It Checks |
|---|---|---|---|
RFC1035-4.1.1-OPCODE |
§4.1.1 | MUST | OPCODE is a defined value (0,1,2,4,5) |
RFC1035-4.1.1-RCODE |
§4.1.1 | MUST | Response RCODE is defined (0-5) |
RFC1035-4.1.1-ZBITS |
§4.1.1 | MUST | Z bits are zero in non-EDNS messages |
RFC1035-4.1.1-QR |
§4.1.1 | MUST | QR bit is 0 or 1 |
RFC1035-4.1.1-TC |
§4.1.1 | MUST | TC bit not set in queries |
RFC1035-4.1.1-AA |
§4.1.1 | SHOULD | AA bit only in responses |
RFC1035-4.1.1-RA |
§4.1.1 | SHOULD | RA bit only in responses |
RFC1035-4.1.2-QDCOUNT |
§4.1.2 | MUST | Standard query has QDCOUNT=1 |
RFC1035-4.1.2-QCLASS |
§4.1.2 | SHOULD | QCLASS is a recognized value |
RFC1035-4.1-QDCOUNT-RESP |
§4.1 | SHOULD | Response echoes question section |
RFC1035-4.1-COUNTS |
§4.1 | MUST | Header counts match actual records |
RFC1035-4.1-NOERROR-ANS |
§4.1 | SHOULD | NODATA detection (informational) |
RFC1035-2.3.4-LABEL-LEN |
§2.3.4 | MUST | Label <= 63 octets |
RFC1035-2.3.4-NAME-LEN |
§2.3.4 | MUST | Domain name <= 253 characters |
RFC1035-2.3.1-LDH |
§2.3.1 | SHOULD | LDH syntax (underscore-prefix exempt) |
RFC1035-4.2.1-UDP-SIZE |
§4.2.1 | MUST | UDP message <= 512 bytes without EDNS |
RFC1035-3.6.2-CNAME-ONLY |
§3.6.2 | MUST | CNAME doesn't coexist with other types |
RFC 6891 — EDNS(0) (8 rules)
| Rule ID | Section | Level | What It Checks |
|---|---|---|---|
RFC6891-6.1.1-OPT-COUNT |
§6.1.1 | MUST | At most one OPT record |
RFC6891-6.1.1-OPT-SECTION |
§6.1.1 | MUST | OPT in additional section only |
RFC6891-6.1.1-OPT-NAME |
§6.1.1 | MUST | OPT NAME is root ('.') |
RFC6891-6.1.3-VERSION |
§6.1.3 | MUST | EDNS version is 0 |
RFC6891-6.2.3-UDP-SIZE |
§6.2.3 | SHOULD | UDP payload size >= 512, != 0 |
RFC6891-6.1.1-EDNS-Z |
§6.1.1 | MUST | EDNS Z reserved bits are zero |
RFC6891-7-EDNS-RESP |
§7 | MUST | EDNS response requires EDNS query (placeholder) |
RFC6891-7-FORMERR-OPT |
§7 | MUST | FORMERR response has no OPT |
Transaction-Level Rules (4 rules)
These pair queries with responses by (IP, TXID):
| Rule ID | What It Checks |
|---|---|
RFC1035-4.1.1-TXID-MATCH |
Response TXID matches query |
RFC1035-4.1-QUESTION-ECHO |
Response question matches query question |
RFC6891-7-EDNS-PAIR |
EDNS in response only if query had EDNS |
RFC1035-4.1.1-TC-TRUNCATION |
Truncated response flagged for TCP retry |
Total: 29 rules across 2 RFCs + stateful transaction analysis.
Architecture
rfc_lint/
├── _scapy.py # Scapy compatibility shim
├── models.py # Severity, Verdict, RuleMetadata, Violation, LintReport
├── parser.py # DNS packet parser (Scapy → structured dict)
├── engine.py # BaseRule, RuleRegistry
├── transaction.py # Stateful query↔response pairing + cross-packet rules
├── linter.py # Orchestrator + text/JSON report formatters
├── cli.py # Click CLI
└── rules/
├── rfc1035.py # 17 RFC 1035 rules
└── rfc6891.py # 8 RFC 6891 rules
tests/
├── fixtures.py # 30 crafted pcap generators
└── test_rules.py # 75 tests (positive + negative for every rule)
Design principles:
- Each rule is a self-contained class with metadata (RFC, section, severity) and a
check()method - Rules are declarative — adding new RFCs means adding rule files, not modifying the engine
- Stateful analysis (transaction pairing) is separate from per-packet rules
- MUST violations → FAIL, SHOULD violations → WARN
Adding New Rules
from rfc_lint.engine import BaseRule
from rfc_lint.models import RuleMetadata, Severity, Violation
class MyNewRule(BaseRule):
@property
def metadata(self):
return RuleMetadata(
rule_id="RFC7858-4.2-TLS-VERSION",
rfc=7858, section="4.2", severity=Severity.MUST,
title="DoT must use TLS 1.2+",
description="DNS over TLS connections must use TLS 1.2 or later.")
def check(self, p: dict) -> list:
# Your check logic here
return []
Output Formats
Text — human-readable report with violation details and summary by rule.
JSON — machine-parseable with full violation metadata for CI/CD integration.
Test Suite
pytest tests/ -v # 75 tests, ~1 second
Every rule has both positive (violation detected) and negative (clean packet passes) tests. Edge cases include IPv6, TCP DNS, EDNS DO bit, underscore service labels, NOTIFY/UPDATE opcodes, NXDOMAIN/SERVFAIL responses, and multi-violation packets.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dns_rfc_lint-1.0.0.tar.gz.
File metadata
- Download URL: dns_rfc_lint-1.0.0.tar.gz
- Upload date:
- Size: 32.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0084121dc2fe4d8c6771591dc54f1927c070ed32b1a88f48ecc01a0fdbb7c363
|
|
| MD5 |
ec52562756994360e4dd8f7b8ed9c541
|
|
| BLAKE2b-256 |
9ff30eadc6adf3e8e859b7ebd7e9cb014a9136572690d0e9050b536e65e35cc1
|
File details
Details for the file dns_rfc_lint-1.0.0-py3-none-any.whl.
File metadata
- Download URL: dns_rfc_lint-1.0.0-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00f219f2ee3f23ffb9431d15e7a452268049950fc9d1d7483e8b63369dbf31d7
|
|
| MD5 |
0460b4b24f1cfea054c9dd96750e293c
|
|
| BLAKE2b-256 |
4d215a28e3e37e02900cba4457164a894b3952dbd624614b09ccc911dc31f3d9
|