Skip to main content

Command Palette

Search for a command to run...

DNS as a Protocol (Part 3)

Updated
17 min readView as Markdown

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
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