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
--- /dev/null
+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 -> <<String.length(part)::integer-size(8)>> <> part <> <<0::8>> end)
+ |> Enum.join(".")
+ 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)>>
+ 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
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),
_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(
- <<n::integer-size(8), data::bitstring-size(n * 8), 0::8, _rest::bitstring>>,
+ <<n::integer-size(8), data::bitstring-size(n * 8), 0::8, type::integer-size(16),
+ class::integer-size(16), _rest::bitstring>>,
parts
) do
- [data | parts]
+ {Enum.reverse([data | parts]), type, class}
end
defp parse_question(
--- /dev/null
+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
use GenServer
alias DnsPhrases.Message.Parser
+ alias DnsPhrases.Message.Encoder
+ alias DnsPhrases.Reply
defmodule State do
defstruct([:socket])
@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