Inspired by EmilHernvall/dnsguide: A guide to writing a DNS Server from scratch in Rust, this project is my attempt to write a DNS server in Elixir.
For now, this server only supports querying an A
record for one domain.
You can open iex -S mix
, and spin up a server like this:
{:ok, server} = DNS.Server.start
# =>
# {:ok, #PID<0.191.0>}
DNS.Server.recursive_lookup(server, "baidu.com")
# =>
# {:ok,
# %DNS.Packet{
# additionals: [
# %{addr: {202, 108, 22, 220}, domain: "dns.baidu.com", ttl: 86400, type: :A},
# %{addr: {220, 181, 33, 31}, domain: "ns2.baidu.com", ttl: 86400, type: :A},
# %{addr: {112, 80, 248, 64}, domain: "ns3.baidu.com", ttl: 86400, type: :A},
# %{addr: {14, 215, 178, 80}, domain: "ns4.baidu.com", ttl: 86400, type: :A},
# %{addr: {180, 76, 76, 92}, domain: "ns7.baidu.com", ttl: 86400, type: :A}
# ],
# answers: [
# %{addr: {39, 156, 69, 79}, domain: "baidu.com", ttl: 600, type: :A},
# %{addr: {220, 181, 38, 148}, domain: "baidu.com", ttl: 600, type: :A}
# ],
# authorities: [
# %{domain: "baidu.com", host: "ns4.baidu.com", ttl: 86400, type: :NS},
# %{domain: "baidu.com", host: "dns.baidu.com", ttl: 86400, type: :NS},
# %{domain: "baidu.com", host: "ns2.baidu.com", ttl: 86400, type: :NS},
# %{domain: "baidu.com", host: "ns7.baidu.com", ttl: 86400, type: :NS},
# %{domain: "baidu.com", host: "ns3.baidu.com", ttl: 86400, type: :NS}
# ],
# header: %DNS.Packet.Header{
# additional_count: 5,
# answer_count: 2,
# authoritative_answer: true,
# authority_count: 5,
# id: 15943,
# operation_code: 0,
# query_response: true,
# question_count: 1,
# recursion_available: false,
# recursion_desired: false,
# reserved: 0,
# response_code: 0,
# truncated_message: false
# },
# questions: [%DNS.Packet.Question{name: "baidu.com", type: :A}]
# }}
The server implement is still in a simple and early stage. But I’ve already learned a ton:
- How to parse a DNS Packet
Parsing a DNS Packet correctly is one of the key foundations for a workable DNS Server. Below are the steps I took to understand the packet format, implement it in Elixir, and improve the implementation:
- Understanding the Format
To understand the format, the best place is the original document that described the DNS implementation and specification: RFC 1035 - Domain names - implementation and specification.
Several fun facts I learned from this documentation:
- What’s the syntax of a domain.
- Why domain names are case-insensitive.
- Basic Pattern Matching
- Parser Combinator
- Readability
- Benchmark
- Understanding the Format
- How to work with Dialyzer
- How to build a UDP Server in Elixir
- Adding concurrency to an Elixir program is like a breeze
- Parse domains as FQDN
- Add support to more resource record types (:TXT, :SOA, :ALIAS, etc.)
- Caching
- Extract functional core from
DNS.Server