DNS as a Protocol (Part 3)
EDNS
Introduction {#introduction}
DNS was designed in the 1980s when hardware was slow, networks were limited, and the internet was tiny. The original protocol made sense then.
But we're not in the 1980s anymore.
EDNS (Extensions for DNS) is how the DNS protocol evolved to meet modern demands while maintaining backward compatibility with ancient systems.
Understanding EDNS helps you grasp:
How DNS handles modern network sizes
Why security extensions are possible
How content delivery networks optimize performance
How DNS continues to serve billions of users
The clever engineering behind protocol evolution
Why EDNS Was Needed {#why-needed}
The Evolution Challenge
DNS is mission-critical infrastructure. You cannot simply "replace" DNS protocol because:
Problems with Replacing DNS:
1. Billions of Devices
├─ Computers
├─ Phones
├─ Servers
├─ IoT devices
├─ Network equipment
└─ All expect DNS to work
2. Ancient Systems Still in Use
├─ 20+ year old routers
├─ Legacy applications
├─ Embedded systems
├─ Specialized hardware
└─ Can't be easily updated
3. Global Scale
├─ Millions of nameservers
├─ Billions of queries daily
├─ No central control
├─ Independent operators
└─ Can't coordinate shutdown
4. Critical Service
├─ Internet depends on it
├─ Downtime = catastrophic
├─ Can't risk breaking things
├─ Must be extremely stable
└─ Change management critical
Result:
└─ Must evolve without breaking things
└─ Must be backward compatible
└─ Must allow optional features
└─ Solution: EDNS
What Needed to Change
Growing Internet Demands:
1980s Internet: Small and Simple
├─ Few domains
├─ Small responses
├─ Limited features
├─ Basic queries
└─ Simple infrastructure
2000s Internet: Growing
├─ Millions of domains
├─ More complex records
├─ New record types
├─ Performance concerns
└─ Starting to hit limits
2020s Internet: Massive and Complex
├─ Billions of domains
├─ Large responses (DNSSEC)
├─ Many record types
├─ Security critical
├─ Global-scale requirements
├─ New features needed
└─ Original design insufficient
Original DNS Limitations
The 512-Byte Problem
Original DNS (RFC 1035, 1987):
UDP Packet Size Limit: 512 bytes
Why 512?
IPv4 Standard (RFC 791):
├─ Minimum datagram size: 576 bytes
├─ IP header: Up to 60 bytes
├─ UDP header: 8 bytes
├─ Available for DNS data: 576 - 60 - 8 = 508 bytes
├─ Rounded to: 512 bytes
└─ "Safe size" for all systems
Impact:
Small Response:
├─ A record: 16 bytes of data
├─ Fits easily in 512 bytes
└─ No problem
Medium Response:
├─ Multiple A records: 64 bytes
├─ Still fits in 512 bytes
└─ No problem
Large Response:
├─ DNSSEC signed response: 600+ bytes
├─ EXCEEDS 512 bytes
├─ Problem! Truncated (TC flag set)
└─ Fallback to TCP
Result:
├─ Responses truncated
├─ Fallback to TCP (slower)
├─ Performance degradation
├─ Modern features impossible
└─ Clearly a limitation
Limited Response Codes
Original DNS Response Codes (RCODE):
Header Field: 4 bits
Maximum Value: 2^4 = 16 possible values
Range: 0-15
Original RCODEs (0-5):
├─ 0: NOERROR (success)
├─ 1: FORMERR (format error)
├─ 2: SERVFAIL (server failure)
├─ 3: NXDOMAIN (name doesn't exist)
├─ 4: NOTIMP (not implemented)
├─ 5: REFUSED (refused)
└─ 6-15: Reserved for future use
Problem:
Modern Needs:
├─ DNSSEC validation failure
├─ Cookie validation failure
├─ Other security errors
├─ More specific error codes
└─ Only 10 unused codes available
With only 10 unused codes, future extensions very limited
No Capability Signaling
Original DNS Communication:
Client:
├─ Sends query
└─ No information about capabilities
Server:
├─ Receives query
├─ Doesn't know what client supports
├─ Guesses what features to use
├─ May assume limited client
└─ Can't send modern features
Problem:
No Way to Say:
├─ "I support large responses"
├─ "I want DNSSEC validation"
├─ "I support new record types"
├─ "I want extended information"
└─ "I support new features"
Result:
├─ Servers must assume minimal features
├─ Can't use advanced capabilities
├─ No way to negotiate
└─ Stuck with 1987 baseline
The Catch-22
Situation:
Clients want:
├─ Larger responses
├─ More features
├─ Better security
└─ Modern functionality
Servers want:
├─ Send larger responses
├─ Provide new features
├─ Improve security
└─ Evolve the protocol
But...
Clients can't tell servers what they support
Servers don't know what clients understand
Both stuck with 1987 limitations
No way to signal new capabilities
Catch-22: Can't evolve without signaling!
Solution: EDNS
EDNS History and Development
Timeline
1987: RFC 1035 - Original DNS
├─ 512-byte limit
├─ 4-bit response codes
├─ No extensibility mechanism
└─ Designed for 1980s internet
1999: RFC 2671 - EDNS0 Introduced
├─ Extension mechanism proposed
├─ OPT pseudo-record concept
├─ Backward compatible approach
├─ Uses additional section
└─ Clever repurposing of existing fields
2000s: Gradual Adoption
├─ DNS servers add EDNS support
├─ Resolvers begin using EDNS
├─ Slow deployment (legacy systems)
└─ Some resistance initially
2013: EDNS Becomes Mandatory
├─ ICANN mandates EDNS support
├─ All resolvers must support EDNS
├─ All nameservers must support EDNS
├─ De facto standard now
└─ Legacy systems phase out
2010s-2020s: Feature Expansion
├─ Client subnet extension
├─ Cookie mechanism
├─ DNSSEC integration
├─ DoT and DoH support
├─ Continued evolution
└─ EDNS enables modern DNS
RFC Standards
Core EDNS RFCs:
RFC 2671 (1999)
├─ Original EDNS0 specification
├─ OPT pseudo-record format
├─ UDP payload size
└─ Extended RCODE values
RFC 2845 (2000)
├─ TSIG (Transaction Signature)
├─ DNS message authentication
└─ Used with EDNS
RFC 3225 (2002)
├─ Identification of DNSSEC-aware resolver
├─ DNSSEC DO flag
└─ Signals DNSSEC support
RFC 6891 (2013)
├─ EDNS0 specification update
├─ Security considerations
├─ UDP payload size recommendations
└─ Best practices
RFC 7871 (2016)
├─ Client subnet in EDNS0
├─ GeoDNS optimization
└─ Location-based responses
Additional RFCs:
├─ RFC 6844 - CAA records
├─ RFC 7553 - URI records
└─ Many more record types
OPT Pseudo-Records {#opt-records}
What is an OPT Record?
OPT Record:
Definition:
├─ Pseudo-resource record (not a real record)
├─ Used only in additional section
├─ Signals EDNS capabilities
├─ Used by DNS software, not users
└─ Transparent to most people
Purpose:
├─ Enable EDNS functionality
├─ Signal client capabilities
├─ Carry EDNS options
├─ Maintain backward compatibility
└─ Allow protocol evolution
Location:
├─ Only in additional section
├─ RFC 1035 allows additional section
├─ No modification to message format
├─ Existing DNS still works
└─ Clever and elegant solution
Visibility:
├─ Visible with dig command
├─ Shows as OPT PSEUDOSECTION
├─ Not visible to users normally
└─ Infrastructure level
OPT Record Example
Dig output showing OPT record:
$ dig kubernetes.io
;; OPT PSEUDOSECTION:
;; EDNS: version: 0, flags:; udp: 512
Breakdown:
├─ OPT PSEUDOSECTION: Special section
├─ EDNS: Extended DNS enabled
├─ version: 0: EDNS version 0 (stable)
├─ flags:: Empty flags (for now)
└─ udp: 512: Client supports 512-byte UDP
Meaning:
├─ DNS query includes EDNS
├─ Client supports EDNS version 0
├─ Client supports 512-byte responses
└─ Server should include OPT in response
How OPT Records Work
Query with OPT:
Client sends:
├─ Regular DNS query (standard)
├─ Plus OPT record in additional section
│ └─ "I support EDNS0"
│ └─ "I can handle 512+ byte responses"
│ └─ "I want DNSSEC validation"
│ └─ "Include my client subnet"
└─ Rest of message unchanged
Server receiving OPT:
Server checks:
├─ Does message have OPT record?
├─ If yes: Server supports EDNS
├─ If no: Server ignores OPT
└─ Server must be EDNS-aware
Server processes:
├─ Reads OPT flags
├─ Checks UDP payload size
├─ Notes any special requests
├─ Prepares appropriate response
└─ Includes OPT in response
Response with OPT:
Server sends:
├─ DNS answer (standard)
├─ OPT record in additional section
│ └─ "I support EDNS0"
│ └─ "I can send larger responses"
│ └─ "DNSSEC validation included"
│ └─ "Here are EDNS options"
└─ Message format unchanged
Backward Compatibility:
Old Server without EDNS:
├─ Receives query with OPT
├─ Doesn't understand OPT
├─ Ignores OPT record
├─ Sends regular response (no OPT)
├─ Client detects no OPT
├─ Uses traditional mode
└─ Everything still works!
Old Client without EDNS:
├─ Sends query (no OPT)
├─ New server receives
├─ Detects no OPT in query
├─ Sends regular response (smaller)
├─ Old client processes normally
└─ Everything still works!
S Record Structure {#record-structure}
OPT Record Repurposing
The genius of EDNS is how it repurposes existing DNS record fields:
Standard DNS Resource Record:
Name (domain name)
Type (A, AAAA, CNAME, etc.)
Class (usually IN)
TTL (Time to Live)
RDLENGTH (data length)
RDATA (actual data)
EDNS OPT Record (Repurposing):
Field Standard Meaning EDNS OPT Meaning
─────────────────────────────────────────────────────────
Name Domain name Root (empty)
Type Record type 41 (OPT)
Class Record class UDP Payload Size
TTL Time to live Extended RCODE + Flags
RDLENGTH Data length RDLENGTH (same)
RDATA Record data EDNS Options
Genius:
├─ Uses existing message format
├─ No protocol changes needed
├─ Old software ignores if unknown
├─ New software interprets correctly
└─ Perfect backward compatibility!
Detailed Field Mapping
Name Field
Standard DNS:
├─ Contains domain name (e.g., example.com)
└─ Identifies the record
EDNS OPT:
├─ Set to empty root domain (.)
├─ All OPT records use root
└─ Signals "zone-wide" configuration
Type Field
Standard DNS:
├─ 1 = A record
├─ 2 = NS record
├─ 5 = CNAME record
└─ etc. (various types)
EDNS OPT:
├─ Type value: 41 (assigned specifically)
├─ Means: This is an OPT record
├─ OPT records always type 41
└─ Identifies record as EDNS
Class Field (UDP Payload Size)
Standard DNS:
├─ IN = Internet (universal)
├─ CH = Chaos (rare)
└─ HS = Hesiod (obsolete)
EDNS OPT (Repurposed as UDP Payload):
├─ Value: 512-4096 (bytes)
├─ Example: 512 (original size)
├─ Example: 4096 (modern EDNS)
├─ Tells server: "I can handle N bytes"
└─ Server respects this limit
TTL Field (Extended RCODE + Flags)
Standard DNS TTL:
├─ Time to cache in seconds
├─ Example: 3600 (one hour)
└─ Used for caching
EDNS OPT (Repurposed):
├─ Upper 8 bits: Extended RCODE (0-255)
│ └─ Extends original 4-bit RCODE
│ └─ Allows more error codes
│ └─ Original 0-15, extended 16+
├─ Next 4 bits: EDNS version
│ └─ Currently 0 (EDNS0)
│ └─ Reserved for future versions
└─ Lower 4 bits: Flags
├─ DO flag: DNSSEC OK (bit 0)
└─ Other flags: Reserved
RDLENGTH Field
Standard DNS:
├─ Length of RDATA in bytes
└─ Variable depending on record
EDNS OPT:
├─ Length of EDNS options
├─ 0 if no options
├─ Variable if options present
└─ Same as standard
RDATA Field (EDNS Options)
Standard DNS:
├─ Record-specific data
├─ A record: IPv4 address (4 bytes)
├─ AAAA record: IPv6 address (16 bytes)
└─ etc.
EDNS OPT:
├─ EDNS-specific options
├─ Option type: 1-65535
├─ Option data: Variable
├─ Examples:
│ ├─ Client subnet (option 8)
│ ├─ Cookie (option 10)
│ ├─ TAO (option 34)
│ └─ More defined by RFCs
└─ Each option has TLV format
UDP Payload Size Enhancement {#udp-payload}
Original 512-Byte Limitation
Standard DNS Query/Response:
Typical Small Query:
├─ Header: 12 bytes
├─ Question: 20 bytes
└─ Total: ~32 bytes (fits easily)
Typical Small Response:
├─ Header: 12 bytes
├─ Question: 20 bytes
├─ Answer: 16 bytes (A record)
└─ Total: ~48 bytes (fits easily)
Medium Response (Multiple A Records):
├─ Header: 12 bytes
├─ Question: 20 bytes
├─ 10 A records: 160 bytes
├─ Authority: 50 bytes
└─ Total: ~242 bytes (fits easily)
Large Response (DNSSEC):
├─ Header: 12 bytes
├─ Question: 20 bytes
├─ A record: 16 bytes
├─ RRSIG record: 150 bytes
├─ DNSKEY record: 300 bytes
├─ Other records: 100 bytes
└─ Total: ~598 bytes (EXCEEDS 512!)
Result: Response truncated (TC flag set)
Effect: Client must retry with TCP
Performance: Much slower
EDNS UDP Payload Size
Modern EDNS Payload Sizes:
Default (No EDNS):
├─ 512 bytes
└─ Original size
With EDNS Support:
├─ Client advertises support
├─ Example: 4096 bytes
├─ Server can send up to 4096
└─ Larger responses possible
Negotiation:
Client Query:
├─ Includes OPT record
├─ Class field: 4096
└─ "I can handle up to 4096 bytes"
Server Response:
├─ If response fits in 4096: Send full
├─ If response exceeds 4096: Truncate (TC flag)
├─ Client can retry with TCP
└─ But much rarer now
Benefits:
1. Larger Responses
├─ DNSSEC signed responses fit
├─ Multiple records fit
├─ No truncation needed
└─ Better performance
2. Fewer TCP Connections
├─ UDP handles most queries
├─ TCP only for rare large responses
├─ Faster overall
└─ Less server load
3. Security Extensions
├─ DNSSEC requires space
├─ Signatures must fit
├─ More security possible
└─ Better protection
Recommended Sizes
RFC 6891 Guidance:
Minimum:
├─ 512 bytes
├─ Safe for all systems
└─ Backward compatible
Recommended:
├─ 4096 bytes
├─ Balances size and performance
├─ Handles most modern needs
└─ Industry standard
Maximum (Not Recommended):
├─ Above 4096 bytes
├─ Security risks
├─ Amplification attack potential
├─ Not recommended
└─ Stick with 4096
Security Consideration:
UDP Amplification Attacks:
├─ Attacker sends small query
├─ Server responds with huge packet
├─ 1 byte query → 4096 byte response
├─ 4000x amplification!
├─ Can overwhelm targets
Mitigation:
├─ Rate limiting
├─ Source validation
├─ BCP 38 (ingress filtering)
└─ Recommended 4096 limit
Practical Recommendation:
├─ Use 4096 bytes for EDNS
├─ Still supports modern features
├─ Not so large as to be risky
└─ Good balance point
Client Subnet Extension
Why Client Subnet Matters
Original DNS Problem:
Without Client Subnet:
1. Client in Tokyo queries
2. Sends to ISP resolver (also Tokyo)
3. ISP resolver queries nameserver
4. Nameserver sees: ISP resolver IP
5. Nameserver doesn't know:
├─ Client is in Tokyo
├─ Client location
├─ Client network
└─ Anything about actual client!
6. Result: Can't optimize for location
With Client Subnet (EDNS):
1. Client in Tokyo queries
2. ISP resolver includes client subnet
└─ "Query from 202.xx.0.0/24 (Tokyo)"
3. Resolver queries nameserver
4. Nameserver sees: Client subnet info
5. Nameserver can:
├─ Determine client location
├─ Return geographically optimal answer
├─ Route to nearest server
└─ Optimize performance
6. Result: Geolocation-based optimization
Client Subnet Option Format
EDNS Option 8: Client Subnet
Format:
Option Code: 8
Option Length: Variable
Family: 1 (IPv4) or 2 (IPv6)
Source Prefix Length: Bits of client IP to use
Scope Prefix Length: Bits server should use
Client Subnet: Client's IP prefix
Example (IPv4):
Client IP: 192.0.2.50
Prefix Length: /24 (first 3 octets)
Sent as: 192.0.2.0/24
Meaning: "Query from 192.0.2.x network"
Example (IPv6):
Client IP: 2001:db8::1
Prefix Length: /64
Sent as: 2001:db8::/64
Meaning: "Query from 2001:db8::/64 network"
GeoDNS and CDN Optimization
CDN Use Case:
NetFlix in Tokyo wants optimal server:
1. Client (Tokyo) → ISP resolver (Tokyo)
2. Resolver adds client subnet:
└─ EDNS option 8: 202.20.0.0/24 (Tokyo)
3. Query to Netflix nameserver
4. Netflix nameserver:
├─ Reads client subnet (Tokyo)
├─ Checks: Nearest server to Tokyo
├─ Options: Tokyo, Singapore, Sydney
├─ Selects: Tokyo (nearest)
└─ Returns: Tokyo server IP
5. Client connects to Tokyo server
6. Fast streaming!
Without Client Subnet:
1. Client (Tokyo) → ISP resolver
2. Query to Netflix nameserver
3. Netflix nameserver sees:
├─ Query from: ISP IP
├─ Can't determine location
├─ Random server selection
├─ Might return Sydney server
└─ Wrong location!
4. Client connects to Sydney server
5. Slow streaming (7000+ miles away)
6. Poor experience
Backward Compatibility {#backward-compatibility}
The Critical Feature
EDNS's greatest achievement is backward compatibility with ancient systems.
Old Server Behavior (No EDNS Support):
Old Server receives:
├─ Query (standard format)
├─ Plus OPT record (doesn't understand)
├─ Processes query
├─ Ignores OPT record
└─ Sends response (no OPT)
Old Client behavior:
├─ Query sent (no OPT)
├─ Server processes normally
├─ Response received
└─ No OPT expected
Result: Everything works normally!
New Server with Old Client:
New Server receives:
├─ Query (standard format)
├─ No OPT record
├─ Detects: Client doesn't support EDNS
├─ Sends standard response (512 bytes max)
├─ Uses basic features
└─ Matches client capabilities
Old Client with New Server:
Client sends:
├─ Query (standard, no OPT)
├─ Server detects: No EDNS
├─ Server responds: Standard response
└─ Client processes normally
New Client with Old Server:
Client sends:
├─ Query with OPT record
├─ Old server receives OPT
├─ Old server ignores OPT
├─ Sends standard response (512 bytes)
├─ Client detects: No OPT in response
├─ Client knows: Server doesn't support EDNS
├─ Client uses fallback mode
└─ Everything works
Perfect Compatibility:
├─ New always works with old
├─ Old always works with new
├─ Graceful degradation
├─ No breaking changes
└─ Universal compatibility
Why This Matters
Problem with Requiring New Version:
If DNS required upgrade:
├─ Billions of devices
├─ Millions of servers
├─ 20+ year old equipment
├─ Impossible to coordinate
└─ Internet would break!
Solution: EDNS Optional
EDNS is optional:
├─ Works without it (backward compatible)
├─ Works with it (forward compatible)
├─ Devices choose level of support
├─ No forced upgrades
├─ Graceful rollout possible
└─ Problem solved!
Modern Adoption:
2013: EDNS becomes mandatory
├─ By ICANN policy
├─ All new software supports EDNS
├─ All modern servers use EDNS
├─ But old systems still work
└─ 10+ years of graceful transition
Current State (2024):
EDNS is nearly universal:
├─ ~99% of infrastructure supports EDNS
├─ ~1% ancient systems remain
├─ Old systems still work (fallback)
├─ New features use EDNS
├─ All systems coexist happily
└─ Backward compatibility preserved
EDNS in Practice {#edns-practice}
Using Dig to See EDNS
Basic Dig Query:
$ dig example.com
Output shows OPT:
;; OPT PSEUDOSECTION:
;; EDNS: version: 0, flags:; udp: 512
Interpretation:
├─ EDNS: DNS supports EDNS
├─ version 0: EDNS version 0 (stable)
├─ flags: (empty): No special flags
└─ udp: 512: Supports 512-byte UDP
Dig with DNSSEC:
$ dig +dnssec example.com
Output shows:
;; OPT PSEUDOSECTION:
;; EDNS: version: 0, flags: do; udp: 512
New field:
├─ flags: do: DO flag set
├─ Meaning: DNSSEC OK
└─ Want: DNSSEC records included
Dig with Custom UDP Size:
$ dig +bufsize=4096 example.com
Output shows:
;; OPT PSEUDOSECTION:
;; EDNS: version: 0, flags:; udp: 4096
New value:
├─ udp: 4096: Server can handle 4096 bytes
└─ Client advertising: "Send me large responses"
EDNS in Different Scenarios
Scenario 1: Simple Query
$ dig google.com
Result:
├─ Query sent with EDNS
├─ Server responds with EDNS
├─ Standard answer
├─ EDNS transparent to user
└─ Works perfectly
Scenario 2: Large DNSSEC Response
$ dig +dnssec google.com
Result:
├─ Query includes DO flag
├─ Server includes DNSSEC records
├─ Response larger than 512 bytes
├─ Fits in EDNS 512+ byte support
├─ All DNSSEC signatures included
└─ Client can validate
Scenario 3: Zone Transfer
$ dig @ns.example.com example.com AXFR
Result:
├─ Zone transfers use TCP
├─ Can be very large
├─ Uses standard TCP (no 512-byte limit)
├─ EDNS not needed here
└─ Works with standard TCP
Scenario 4: Old System Query
Old server without EDNS:
$ dig @oldserver.example.com example.com
Result:
├─ Client detects: No OPT in response
├─ Client knows: Server is old
├─ Client uses fallback
├─ Standard 512-byte mode
├─ Everything still works
└─ Graceful degradation