Project 2: MX Record Resolver and Mail Router

Build a DNS tool that resolves MX records, applies priority logic, and falls back to A/AAAA when needed.

Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1-2 weeks
Language C (Alternatives: Go, Rust, Python)
Prerequisites DNS basics, UDP sockets, byte parsing
Key Topics DNS message format, MX priority, name compression

1. Learning Objectives

  1. Construct raw DNS queries for MX and A/AAAA records.
  2. Parse DNS responses with compression pointers.
  3. Apply MX priority ordering and fallback rules.
  4. Resolve hostnames to IPs and present delivery order.

2. Theoretical Foundation

2.1 Core Concepts

  • DNS packet format: A 12-byte header followed by question and answer sections with variable-length records.
  • Name compression: DNS uses pointers to reuse earlier names, reducing packet size but complicating parsing.
  • MX semantics: Multiple MX records specify preference; lower value is higher priority.
  • Implicit MX: If no MX exists, RFC 5321 allows delivery to A/AAAA.

2.2 Why This Matters

SMTP delivery begins with DNS. If your resolver misparses or mishandles preference, you deliver to the wrong server or fail entirely.

2.3 Historical Context / Background

DNS predates SMTP authentication. The design optimized for small messages, hence label encoding and compression pointers.

2.4 Common Misconceptions

  • Misconception: MX records are always present. Reality: A/AAAA fallback exists.
  • Misconception: DNS replies are simple strings. Reality: They are binary packets with compression.

3. Project Specification

3.1 What You Will Build

A CLI tool that accepts a domain or email address, queries DNS for MX records, parses results, resolves each MX hostname to IP addresses, and prints the delivery order with TTL.

3.2 Functional Requirements

  1. Construct DNS query for MX and send via UDP to a resolver.
  2. Parse MX answers, including name compression.
  3. Sort by preference and list results.
  4. Fallback to A/AAAA if no MX exists.
  5. Resolve hostnames to IPs.

3.3 Non-Functional Requirements

  • Performance: single query under 200 ms on local resolver.
  • Reliability: handle truncated responses (TC bit).
  • Usability: clear warnings for missing MX or DNS failures.

3.4 Example Usage / Output

$ ./mx-resolver gmail.com
MX records for gmail.com
  5  gmail-smtp-in.l.google.com  -> 142.250.152.27
  10 alt1.gmail-smtp-in.l.google.com -> 142.250.115.26
  20 alt2.gmail-smtp-in.l.google.com -> 142.251.9.27
TTL: 300s

3.5 Real World Outcome

You can validate delivery routing for any domain and explain why a server is chosen first, including how fallback behaves when MX records are missing.


4. Solution Architecture

4.1 High-Level Design

CLI
  -> DNS Query Builder
  -> UDP Transport
  -> DNS Parser
  -> MX Sorter
  -> A/AAAA Resolver

4.2 Key Components

Component Responsibility Key Decisions
Query Builder Encode header and question Random ID, recursion desired flag
Parser Decode answers and names Handle compression pointers
Router Sort MX by preference Stable sort for equal priorities
Resolver Resolve MX hostnames Use system resolver or raw queries

4.3 Data Structures

typedef struct {
    uint16_t preference;
    char exchange[256];
    char ip[64];
} mx_record_t;

4.4 Algorithm Overview

Key Algorithm: Name Decompression

  1. Read label length byte.
  2. If top two bits are 11, follow pointer.
  3. Append labels until zero length terminator.

Complexity Analysis:

  • Time: O(n) for n bytes in response
  • Space: O(k) for k records

5. Implementation Guide

5.1 Development Environment Setup

cc -Wall -Wextra -o mx-resolver mx_resolver.c

5.2 Project Structure

mx-resolver/
├── mx_resolver.c
├── dns_packet.c
├── dns_packet.h
└── README.md

5.3 The Core Question You’re Answering

“How does a mail server decide where to deliver a message for a domain?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. DNS Header Fields
    • QDCOUNT, ANCOUNT, RCODE
    • Flags: QR, AA, TC, RD, RA
  2. Name Encoding
    • Label lengths and terminators
    • Compression pointers (0xC0)
  3. MX Semantics
    • Lower preference = higher priority
    • Fallback to A/AAAA if no MX
  4. UDP Behavior
    • MTU limits and truncation
    • Retries on timeout

5.5 Questions to Guide Your Design

  1. How will you avoid infinite loops when following compression pointers?
  2. Will you implement TCP fallback if TC is set?
  3. How will you cache TTLs and reuse results?

5.6 Thinking Exercise

Given:

MX 10 mx1.example.com
MX 20 mx2.example.com

If mx1 is unreachable, what is the correct next step? What does RFC 5321 say about retry intervals?

5.7 The Interview Questions They’ll Ask

  1. “How does DNS name compression work?”
  2. “What happens if a domain has no MX records?”
  3. “Why use UDP for DNS and when do you use TCP?”

5.8 Hints in Layers

Hint 1: Print raw bytes

  • Dump the packet to debug offsets.

Hint 2: Write a recursive name reader

  • Track visited offsets to avoid loops.

Hint 3: Validate counts

  • Use ANCOUNT to control answer parsing.

5.9 Books That Will Help

Topic Book Chapter
DNS message format TCP/IP Illustrated Vol 1 Ch. 11
DNS records Computer Networks Ch. 7
Mail routing RFC 5321 Section 5

5.10 Implementation Phases

Phase 1: Foundation (2-3 days)

Goals:

  • Build DNS query packet
  • Send via UDP

Tasks:

  1. Encode header and question.
  2. Parse header fields in response.

Checkpoint: Receive a valid response to A query.

Phase 2: Core Functionality (4-5 days)

Goals:

  • Parse MX answers
  • Implement name decompression

Tasks:

  1. Decode answer section.
  2. Extract MX preference and exchange.

Checkpoint: Print MX records for a known domain.

Phase 3: Polish and Edge Cases (2-3 days)

Goals:

  • A/AAAA fallback
  • TTL and caching

Tasks:

  1. Implement A/AAAA lookup for each MX.
  2. Add simple TTL cache.

Checkpoint: Handle a domain with no MX.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Compression parsing Recursive vs iterative Recursive with offset guards Simpler to implement
Resolver System resolver vs raw Raw for MX, system for A/AAAA Reduces complexity
TCP fallback Implement vs skip Implement basic retry More correct for large replies

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Name decoding Compression pointer cases
Integration Tests Real domains gmail.com, example.com
Edge Case Tests No MX A/AAAA fallback

6.2 Critical Test Cases

  1. Compressed MX name: ensure correct decoding.
  2. No MX: fallback to A/AAAA.
  3. TC set: retry with TCP or warn.

6.3 Test Data

example.com -> no MX
gmail.com -> multiple MX

7. Common Pitfalls and Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Misreading compression Garbage names Implement pointer resolution
Wrong byte order Invalid values Use ntohs/ntohl
Ignoring TC bit Missing answers Retry with TCP

7.2 Debugging Strategies

  • Compare your packet to dig +noedns output.
  • Use Wireshark to inspect DNS packets.

7.3 Performance Traps

  • Re-resolving the same MX host for each query. Cache results.

8. Extensions and Challenges

8.1 Beginner Extensions

  • Add query for AAAA records.
  • Accept full email addresses and extract domain.

8.2 Intermediate Extensions

  • Implement DNS cache with TTL.
  • Add TCP fallback for large responses.

8.3 Advanced Extensions

  • Implement full recursive resolver.
  • Add DNSSEC validation.

9. Real-World Connections

9.1 Industry Applications

  • Mail servers use this logic for routing and delivery.
  • Deliverability tools verify MX health and failover.
  • dig: https://bind9.readthedocs.io/ - reference DNS client
  • ldns: https://www.nlnetlabs.nl/projects/ldns/ - DNS library

9.3 Interview Relevance

  • DNS parsing and byte-level protocols are standard systems interview topics.

10. Resources

10.1 Essential Reading

  • RFC 1035 - DNS protocol details
  • RFC 5321 - Mail routing and MX semantics

10.2 Video Resources

  • DNS packet parsing walkthroughs

10.3 Tools and Documentation

  • dig and nslookup for verification
  • Wireshark for packet inspection

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain DNS header fields
  • I can decode compressed DNS names
  • I understand MX fallback rules

11.2 Implementation

  • MX results are sorted correctly
  • Fallback to A/AAAA works
  • Errors are clear and actionable

11.3 Growth

  • I can debug DNS failures using packets
  • I can explain why a server was chosen

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Query MX and parse results for a domain
  • Print sorted MX list

Full Completion:

  • Resolve IPs and fallback to A/AAAA
  • Handle compressed names correctly

Excellence (Going Above and Beyond):

  • Implement TCP fallback and caching
  • Provide JSON output mode

This guide was generated from EMAIL_SYSTEMS_DEEP_DIVE_PROJECTS.md. For the complete learning path, see the parent directory.