From a2a4c932f06635659740e51f5c1c8df0b2bd2430 Mon Sep 17 00:00:00 2001 From: Alexander Goussas Date: Sat, 31 Jan 2026 19:46:46 -0500 Subject: [PATCH] feat: start adding encoder --- lib/dns_phrases/message.ex | 9 ++++++-- lib/dns_phrases/message/encoder.ex | 35 ++++++++++++++++++++++++++++++ lib/dns_phrases/message/parser.ex | 26 +++++++++++++--------- lib/dns_phrases/reply.ex | 8 +++++++ lib/dns_phrases/server.ex | 15 ++++++++----- 5 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 lib/dns_phrases/message/encoder.ex create mode 100644 lib/dns_phrases/reply.ex diff --git a/lib/dns_phrases/message.ex b/lib/dns_phrases/message.ex index e0ac29c..6969793 100644 --- a/lib/dns_phrases/message.ex +++ b/lib/dns_phrases/message.ex @@ -1,8 +1,13 @@ defmodule DnsPhrases.Message do - defstruct [:op, tid: 0] + defstruct [:name, :tid, :type, :class] @typedoc """ A message received from a client. """ - @type t :: %DnsPhrases.Message{op: integer, tid: integer} + @type t :: %DnsPhrases.Message{ + name: list(), + tid: integer(), + type: integer(), + class: integer() + } end diff --git a/lib/dns_phrases/message/encoder.ex b/lib/dns_phrases/message/encoder.ex new file mode 100644 index 0000000..2bd8653 --- /dev/null +++ b/lib/dns_phrases/message/encoder.ex @@ -0,0 +1,35 @@ +defmodule DnsPhrases.Message.Encoder do + alias DnsPhrases.Reply + alias DnsPhrases.Message + + @spec encode(Reply.t()) :: binary() + def encode(%Reply{reply_to: reply_to, phrase: phrase}) do + << + reply_to.tid::integer-size(16), + 1::1, + 0::15, + 1::16, + 1::16, + 0::32 + >> <> + encode_question(reply_to) <> + encode_answer(reply_to, phrase) + end + + @spec encode_name(Message.t()) :: binary() + defp encode_name(msg) do + msg.name + |> Enum.map(fn part -> <> <> part <> <<0::8>> end) + |> Enum.join(".") + end + + @spec encode_question(Message.t()) :: binary() + defp encode_question(msg) do + encode_name(msg) <> <> + end + + @spec encode_answer(Message.t(), binary) :: binary() + defp encode_answer(msg, phrase) do + encode_name(msg) <> <<16::16, 256::16, 0::32, String.length(phrase)::16>> <> phrase + end +end diff --git a/lib/dns_phrases/message/parser.ex b/lib/dns_phrases/message/parser.ex index 835ab73..5059128 100644 --- a/lib/dns_phrases/message/parser.ex +++ b/lib/dns_phrases/message/parser.ex @@ -1,10 +1,9 @@ defmodule DnsPhrases.Message.Parser do alias DnsPhrases.Message - @spec parse(bitstring) :: DnsPhrases.Message.t() - + @spec parse(bitstring()) :: DnsPhrases.Message.t() def parse(<< - tid::16, + tid::integer-size(16), 0::1, _flags::15, 1::integer-size(16), @@ -13,19 +12,26 @@ defmodule DnsPhrases.Message.Parser do _additional_rrs::16, data::bitstring >>) do - op = parse_question(data) - %Message{tid: tid, op: op} + {name, type, class} = parse_question(data) + + %Message{ + name: name, + type: type, + class: class, + tid: tid + } end - @spec parse_question(bitstring) :: list - def parse_question(data), do: parse_question(data, []) |> Enum.reverse() + @spec parse_question(bitstring) :: {list(), integer(), integer()} + def parse_question(data), do: parse_question(data, []) - @spec parse_question(bitstring, list) :: list + @spec parse_question(bitstring, list) :: {list(), integer(), integer()} defp parse_question( - <>, + <>, parts ) do - [data | parts] + {Enum.reverse([data | parts]), type, class} end defp parse_question( diff --git a/lib/dns_phrases/reply.ex b/lib/dns_phrases/reply.ex new file mode 100644 index 0000000..9a27ab3 --- /dev/null +++ b/lib/dns_phrases/reply.ex @@ -0,0 +1,8 @@ +defmodule DnsPhrases.Reply do + defstruct [:reply_to, :phrase] + + @typedoc """ + The reply sent back to the client. + """ + @type t :: %DnsPhrases.Reply{reply_to: DnsPhrases.Message.t(), phrase: String.t()} +end diff --git a/lib/dns_phrases/server.ex b/lib/dns_phrases/server.ex index fcb5d4a..3f310df 100644 --- a/lib/dns_phrases/server.ex +++ b/lib/dns_phrases/server.ex @@ -2,6 +2,8 @@ defmodule DnsPhrases.Server do use GenServer alias DnsPhrases.Message.Parser + alias DnsPhrases.Message.Encoder + alias DnsPhrases.Reply defmodule State do defstruct([:socket]) @@ -20,14 +22,17 @@ defmodule DnsPhrases.Server do @impl GenServer def handle_info( - {:udp, socket, _ip, _port, data}, + {:udp, socket, ip, port, data}, %State{socket: socket} = state ) do - # TODO 1: Parse DNS message - Parser.parse(data) + msg = Parser.parse(data) + ans = Encoder.encode(%Reply{reply_to: msg, phrase: "Hello, world!"}) + + case :gen_udp.send(socket, ip, port, ans) do + {:error, reason} -> IO.puts(reason) + :ok -> {} + end - # TODO 2: Assemble DNS response as a TXT record containing the frame - # TODO 3: Reply to our client {:noreply, state} end end -- 2.43.0