From b30f7c790bc6672905ddf202d56ea9c4ce6579df Mon Sep 17 00:00:00 2001 From: Alexander Goussas Date: Sun, 1 Feb 2026 14:55:17 -0500 Subject: [PATCH] feat: successfully reply to TXT queries --- lib/dns_phrases/debug/Log.ex | 9 +++++ lib/dns_phrases/message/encoder.ex | 51 ++++++++++++++++++++------- lib/dns_phrases/message/parser.ex | 56 ++++++++++++++++++++---------- lib/dns_phrases/server.ex | 16 ++++++--- 4 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 lib/dns_phrases/debug/Log.ex diff --git a/lib/dns_phrases/debug/Log.ex b/lib/dns_phrases/debug/Log.ex new file mode 100644 index 0000000..bfe7e16 --- /dev/null +++ b/lib/dns_phrases/debug/Log.ex @@ -0,0 +1,9 @@ +defmodule DnsPhrases.Debug.Log do + require Logger + + def and_print(data, msg) do + Logger.info("#{msg}: #{inspect(data)}") + + data + end +end diff --git a/lib/dns_phrases/message/encoder.ex b/lib/dns_phrases/message/encoder.ex index 2bd8653..7b847c1 100644 --- a/lib/dns_phrases/message/encoder.ex +++ b/lib/dns_phrases/message/encoder.ex @@ -2,34 +2,59 @@ defmodule DnsPhrases.Message.Encoder do alias DnsPhrases.Reply alias DnsPhrases.Message + require Logger + + import DnsPhrases.Debug.Log + @spec encode(Reply.t()) :: binary() def encode(%Reply{reply_to: reply_to, phrase: phrase}) do + Logger.info( + "Encoding DNS message: tid=#{reply_to.tid} name=#{inspect(reply_to.name)} type=#{reply_to.type} class=#{reply_to.class}" + ) + + (encode_header(reply_to.tid) <> + encode_question(reply_to) <> + encode_answer(reply_to, phrase)) + |> and_print("Encoded DNS message") + end + + defp encode_header(tid) do << - reply_to.tid::integer-size(16), - 1::1, + tid::integer-size(16)-big, + 1::integer-size(1)-big, 0::15, - 1::16, - 1::16, - 0::32 - >> <> - encode_question(reply_to) <> - encode_answer(reply_to, phrase) + 1::integer-size(16)-big, + 1::integer-size(16)-big, + 0::16, + 0::16 + >> + |> and_print("Encoded header") end @spec encode_name(Message.t()) :: binary() defp encode_name(msg) do - msg.name - |> Enum.map(fn part -> <> <> part <> <<0::8>> end) - |> Enum.join(".") + (msg.name + |> Enum.map(fn part -> <> <> part end) + |> Enum.join("")) <> + <<0::8>> end @spec encode_question(Message.t()) :: binary() defp encode_question(msg) do - encode_name(msg) <> <> + (encode_name(msg) <> <>) + |> and_print("Encoded question") 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 + phrase_length = String.length(phrase) + data_length = 1 + phrase_length + + (encode_name(msg) <> + <<16::integer-size(16)-big, 1::integer-size(16)-big, 60::integer-size(32)-big, + data_length::integer-size(16)-big, + phrase_length::integer-size(8)-big>> <> + phrase) + |> and_print("Encoded answer") end end diff --git a/lib/dns_phrases/message/parser.ex b/lib/dns_phrases/message/parser.ex index 5059128..f2ad615 100644 --- a/lib/dns_phrases/message/parser.ex +++ b/lib/dns_phrases/message/parser.ex @@ -1,27 +1,45 @@ defmodule DnsPhrases.Message.Parser do + require Logger + alias DnsPhrases.Message - @spec parse(bitstring()) :: DnsPhrases.Message.t() - def parse(<< - tid::integer-size(16), - 0::1, - _flags::15, - 1::integer-size(16), - _number_of_answers::16, - _authority_rrs::16, - _additional_rrs::16, - data::bitstring - >>) do - {name, type, class} = parse_question(data) - - %Message{ - name: name, - type: type, - class: class, - tid: tid - } + import DnsPhrases.Debug.Log + + @spec parse(bitstring()) :: {:ok, DnsPhrases.Message.t()} | :error + def parse( + << + tid::integer-size(16), + 0::1, + _flags::15, + 1::integer-size(16), + _number_of_answers::16, + _authority_rrs::16, + _additional_rrs::16, + data::bitstring + >> = dns_message + ) do + Logger.info("Parsing incoming DNS message: #{inspect(dns_message)}") + + {name, type, class} = + data + |> and_print("Parsing question") + |> parse_question() + + Logger.info( + "Successfully parsed message: tid=#{tid}, name=#{inspect(name)}, type=#{type}, class=#{class}" + ) + + {:ok, + %Message{ + name: name, + type: type, + class: class, + tid: tid + }} end + def parse(_data), do: :error + @spec parse_question(bitstring) :: {list(), integer(), integer()} def parse_question(data), do: parse_question(data, []) diff --git a/lib/dns_phrases/server.ex b/lib/dns_phrases/server.ex index 3f310df..c787e1e 100644 --- a/lib/dns_phrases/server.ex +++ b/lib/dns_phrases/server.ex @@ -5,6 +5,8 @@ defmodule DnsPhrases.Server do alias DnsPhrases.Message.Encoder alias DnsPhrases.Reply + require Logger + defmodule State do defstruct([:socket]) end @@ -25,12 +27,16 @@ defmodule DnsPhrases.Server do {:udp, socket, ip, port, data}, %State{socket: socket} = state ) do - msg = Parser.parse(data) - ans = Encoder.encode(%Reply{reply_to: msg, phrase: "Hello, world!"}) + Logger.info("Incoming query from #{inspect(ip)}") + + case Parser.parse(data) do + {:ok, msg} -> + ans = Encoder.encode(%Reply{reply_to: msg, phrase: "Hello, my bro!"}) + Logger.info("Successfully replied to #{inspect(ip)}") + :gen_udp.send(socket, ip, port, ans) - case :gen_udp.send(socket, ip, port, ans) do - {:error, reason} -> IO.puts(reason) - :ok -> {} + :error -> + :gen_udp.send(socket, ip, port, "Invalid DNS message") end {:noreply, state} -- 2.43.0