]> git.frustrated-labs.net Git - dns-phrasex.git/commitdiff
feat: successfully reply to TXT queries
authorAlexander Goussas <[email protected]>
Sun, 1 Feb 2026 19:55:17 +0000 (14:55 -0500)
committerAlexander Goussas <[email protected]>
Sun, 1 Feb 2026 19:55:17 +0000 (14:55 -0500)
lib/dns_phrases/debug/Log.ex [new file with mode: 0644]
lib/dns_phrases/message/encoder.ex
lib/dns_phrases/message/parser.ex
lib/dns_phrases/server.ex

diff --git a/lib/dns_phrases/debug/Log.ex b/lib/dns_phrases/debug/Log.ex
new file mode 100644 (file)
index 0000000..bfe7e16
--- /dev/null
@@ -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
index 2bd8653f2c69f1aeb41414186d77e7ed2355ddd4..7b847c110098e0a3e351f94c7597ddf4ef220b82 100644 (file)
@@ -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 -> <<String.length(part)::integer-size(8)>> <> part <> <<0::8>> end)
-    |> Enum.join(".")
+    (msg.name
+     |> Enum.map(fn part -> <<String.length(part)::integer-size(8)-big>> <> part end)
+     |> Enum.join("")) <>
+      <<0::8>>
   end
 
   @spec encode_question(Message.t()) :: binary()
   defp encode_question(msg) do
-    encode_name(msg) <> <<msg.type::integer-size(16), msg.class::integer-size(16)>>
+    (encode_name(msg) <> <<msg.type::integer-size(16)-big, msg.class::integer-size(16)-big>>)
+    |> 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
index 50591284e100340812f70b5b5fc29bbfddf98434..f2ad6157c2ef4aa6179f9c2777f460c99b158c2e 100644 (file)
@@ -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, [])
 
index 3f310df0d50149a38ca6c8809f109c56133d11bc..c787e1e60e9c4cf26f2c9f674847834663f3ba44 100644 (file)
@@ -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}