Skip to content

Instantly share code, notes, and snippets.

@aerosol
Created May 21, 2025 08:01
Show Gist options
  • Save aerosol/0b6f7491a1726ac2c622f83550b699e5 to your computer and use it in GitHub Desktop.
Save aerosol/0b6f7491a1726ac2c622f83550b699e5 to your computer and use it in GitHub Desktop.
simple_dns_server.ex
defmodule SimpleDNSServer do
@moduledoc """
A simple DNS server that responds to TXT queries with fixed sample values.
"""
# Use 5353 for testing to avoid requiring root privileges for port 53
@dns_port 9999
@txt_records ["v=spf1 include:_spf.example.com ~all", "key1=value1", "key2=value2"]
def start do
{:ok, socket} = :gen_udp.open(@dns_port, [:binary, active: true, ip: {0, 0, 0, 0}])
IO.puts("DNS server running on port #{@dns_port}")
loop(socket)
end
defp loop(socket) do
receive do
{:udp, _socket, client_ip, client_port, query} ->
IO.inspect({client_ip, client_port, query}, label: "Received DNS query")
response = build_response(query)
:gen_udp.send(socket, client_ip, client_port, response)
loop(socket)
end
end
defp build_response(query) do
# Extract the transaction ID (first 2 bytes of the query)
<<transaction_id::16, flags::16, qdcount::16, _rest::binary>> = query
# Build the DNS response header
header = <<
# Transaction ID
transaction_id::16,
# Flags: Standard query response, no error
0b10000100_00000000::16,
# Questions: Echo the number of questions
qdcount::16,
# Answer RRs: 1
1::16,
# Authority RRs: 0
0::16,
# Additional RRs: 0
0::16
>>
# Extract the question section (everything after the header)
<<_header::binary-size(12), question::binary>> = query
# Build the answer section for a single TXT record
txt_value = "v=spf1 include:_spf.example.com ~all"
txt_data = encode_txt_data(txt_value)
answer = <<
# Name: Pointer to the question
0xC00C::16,
# Type: TXT
16::16,
# Class: IN (Internet)
1::16,
# TTL: 60 seconds
60::32,
# Data length
byte_size(txt_data)::16,
# TXT data
txt_data::binary
>>
# Combine the header, question, and answer sections
header <> question <> answer
end
defp encode_txt_data(txt_value) do
# Encode the TXT value as a length-prefixed string
<<byte_size(txt_value)::8, txt_value::binary>>
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment