|
| 1 | +defmodule ExICE.DNS.Message do |
| 2 | + @moduledoc false |
| 3 | + # DNS Message encoder/decoder implementation. |
| 4 | + # See RFC 1035 (DNS) and RFC 6762 (mDNS). |
| 5 | + # The latter, repurposes the top bit of query and rr class. |
| 6 | + # Limitations: |
| 7 | + # * no support for name compression both when decoding and encoding |
| 8 | + |
| 9 | + @type t() :: %__MODULE__{ |
| 10 | + id: non_neg_integer(), |
| 11 | + qr: boolean(), |
| 12 | + opcode: non_neg_integer(), |
| 13 | + aa: boolean(), |
| 14 | + tc: boolean(), |
| 15 | + rd: boolean(), |
| 16 | + ra: boolean(), |
| 17 | + z: non_neg_integer(), |
| 18 | + rcode: non_neg_integer(), |
| 19 | + question: [map()], |
| 20 | + answer: [map()], |
| 21 | + authority: [map()], |
| 22 | + additional: [map()] |
| 23 | + } |
| 24 | + |
| 25 | + defstruct id: 0, |
| 26 | + qr: false, |
| 27 | + opcode: 0, |
| 28 | + aa: false, |
| 29 | + tc: false, |
| 30 | + rd: false, |
| 31 | + ra: false, |
| 32 | + z: 0, |
| 33 | + rcode: 0, |
| 34 | + question: [], |
| 35 | + answer: [], |
| 36 | + authority: [], |
| 37 | + additional: [] |
| 38 | + |
| 39 | + @spec decode(binary()) :: {:ok, t()} | :error |
| 40 | + def decode(data) do |
| 41 | + with {:ok, header, data} <- decode_header(data), |
| 42 | + {:ok, body, <<>>} <- decode_body(data, header) do |
| 43 | + header = Map.drop(header, [:qdcount, :ancount, :nscount, :arcount]) |
| 44 | + msg = Map.merge(header, body) |
| 45 | + struct!(__MODULE__, msg) |
| 46 | + else |
| 47 | + _ -> :error |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + @spec encode(t()) :: binary() |
| 52 | + def encode(message) do |
| 53 | + header = encode_header(message) |
| 54 | + body = encode_body(message) |
| 55 | + header <> body |
| 56 | + end |
| 57 | + |
| 58 | + # PRIVATE FUNCTIONS |
| 59 | + |
| 60 | + defp decode_header( |
| 61 | + <<id::16, qr::1, opcode::4, aa::1, tc::1, rd::1, ra::1, z::3, rcode::4, qdcount::16, |
| 62 | + ancount::16, nscount::16, arcount::16, data::binary>> |
| 63 | + ) do |
| 64 | + header = |
| 65 | + %{ |
| 66 | + id: id, |
| 67 | + qr: qr == 1, |
| 68 | + opcode: opcode, |
| 69 | + aa: aa == 1, |
| 70 | + tc: tc == 1, |
| 71 | + rd: rd == 1, |
| 72 | + ra: ra == 1, |
| 73 | + z: z, |
| 74 | + rcode: rcode, |
| 75 | + qdcount: qdcount, |
| 76 | + ancount: ancount, |
| 77 | + nscount: nscount, |
| 78 | + arcount: arcount |
| 79 | + } |
| 80 | + |
| 81 | + {:ok, header, data} |
| 82 | + end |
| 83 | + |
| 84 | + defp decode_header(_other), do: :error |
| 85 | + |
| 86 | + defp decode_body(data, header) do |
| 87 | + with {:ok, question, data} <- decode_query_section(data, header.qdcount), |
| 88 | + {:ok, answer, data} <- decode_rr_section(data, header.ancount), |
| 89 | + {:ok, authority, data} <- decode_rr_section(data, header.nscount), |
| 90 | + {:ok, additional, data} <- decode_rr_section(data, header.arcount) do |
| 91 | + body = %{question: question, answer: answer, authority: authority, additional: additional} |
| 92 | + {:ok, body, data} |
| 93 | + end |
| 94 | + end |
| 95 | + |
| 96 | + defp decode_query_section(data, qdcount, acc \\ []) |
| 97 | + |
| 98 | + defp decode_query_section(data, 0, acc), do: {:ok, Enum.reverse(acc), data} |
| 99 | + |
| 100 | + defp decode_query_section(data, qdcount, acc) do |
| 101 | + with {:ok, qname, data} <- decode_name(data), |
| 102 | + {:ok, qtype, data} <- decode_type(data), |
| 103 | + {:ok, unicast_response, qclass, data} <- decode_class(data) do |
| 104 | + question = %{ |
| 105 | + qname: qname, |
| 106 | + qtype: qtype, |
| 107 | + qclass: qclass, |
| 108 | + unicast_response: unicast_response |
| 109 | + } |
| 110 | + |
| 111 | + decode_query_section(data, qdcount - 1, [question | acc]) |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + defp decode_rr_section(data, rr_count, acc \\ []) |
| 116 | + |
| 117 | + defp decode_rr_section(data, 0, acc), do: {:ok, Enum.reverse(acc), data} |
| 118 | + |
| 119 | + defp decode_rr_section(data, rr_count, acc) do |
| 120 | + with {:ok, name, data} <- decode_name(data), |
| 121 | + {:ok, type, data} <- decode_type(data), |
| 122 | + {:ok, flush_cache, class, data} <- decode_class(data), |
| 123 | + {:ok, ttl, data} <- decode_ttl(data), |
| 124 | + {:ok, rdata, data} <- decode_rdata(data) do |
| 125 | + rr = %{ |
| 126 | + name: name, |
| 127 | + type: type, |
| 128 | + flush_cache: flush_cache, |
| 129 | + class: class, |
| 130 | + ttl: ttl, |
| 131 | + rdata: rdata |
| 132 | + } |
| 133 | + |
| 134 | + decode_rr_section(data, rr_count - 1, [rr | acc]) |
| 135 | + end |
| 136 | + end |
| 137 | + |
| 138 | + defp decode_name(data, acc \\ []) |
| 139 | + |
| 140 | + defp decode_name(<<0, rest::binary>>, acc) do |
| 141 | + name = |
| 142 | + acc |
| 143 | + |> Enum.reverse() |
| 144 | + |> Enum.join(".") |
| 145 | + |
| 146 | + {:ok, name, rest} |
| 147 | + end |
| 148 | + |
| 149 | + # we don't support pointers right now |
| 150 | + defp decode_name(<<0::2, label_len::6, label::binary-size(label_len), labels::binary>>, acc) do |
| 151 | + decode_name(labels, [label | acc]) |
| 152 | + end |
| 153 | + |
| 154 | + defp decode_name(_, _), do: :error |
| 155 | + |
| 156 | + defp decode_type(<<1::16, data::binary>>), do: {:ok, :a, data} |
| 157 | + defp decode_type(<<2::16, data::binary>>), do: {:ok, :ns, data} |
| 158 | + defp decode_type(<<3::16, data::binary>>), do: {:ok, :md, data} |
| 159 | + defp decode_type(<<4::16, data::binary>>), do: {:ok, :mf, data} |
| 160 | + defp decode_type(<<5::16, data::binary>>), do: {:ok, :cname, data} |
| 161 | + defp decode_type(<<6::16, data::binary>>), do: {:ok, :soa, data} |
| 162 | + defp decode_type(<<7::16, data::binary>>), do: {:ok, :mb, data} |
| 163 | + defp decode_type(<<8::16, data::binary>>), do: {:ok, :mg, data} |
| 164 | + defp decode_type(<<9::16, data::binary>>), do: {:ok, :mr, data} |
| 165 | + defp decode_type(<<10::16, data::binary>>), do: {:ok, :null, data} |
| 166 | + defp decode_type(<<11::16, data::binary>>), do: {:ok, :wks, data} |
| 167 | + defp decode_type(<<12::16, data::binary>>), do: {:ok, :ptr, data} |
| 168 | + defp decode_type(<<13::16, data::binary>>), do: {:ok, :hinfo, data} |
| 169 | + defp decode_type(<<14::16, data::binary>>), do: {:ok, :minfo, data} |
| 170 | + defp decode_type(<<15::16, data::binary>>), do: {:ok, :mx, data} |
| 171 | + defp decode_type(<<16::16, data::binary>>), do: {:ok, :txt, data} |
| 172 | + defp decode_type(<<252::16, data::binary>>), do: {:ok, :afxr, data} |
| 173 | + defp decode_type(<<253::16, data::binary>>), do: {:ok, :mailb, data} |
| 174 | + defp decode_type(<<254::16, data::binary>>), do: {:ok, :maila, data} |
| 175 | + defp decode_type(<<255::16, data::binary>>), do: {:ok, :*, data} |
| 176 | + defp decode_type(_), do: :error |
| 177 | + |
| 178 | + # In mDNS, the top bit has special meaning. |
| 179 | + # See RFC 6762, sec. 18.12 and 18.13. |
| 180 | + defp decode_class(<<top_bit::1, 1::15, data::binary>>), do: {:ok, top_bit == 1, :in, data} |
| 181 | + defp decode_class(<<top_bit::1, 2::15, data::binary>>), do: {:ok, top_bit == 1, :cs, data} |
| 182 | + defp decode_class(<<top_bit::1, 3::15, data::binary>>), do: {:ok, top_bit == 1, :ch, data} |
| 183 | + defp decode_class(<<top_bit::1, 4::15, data::binary>>), do: {:ok, top_bit == 1, :hs, data} |
| 184 | + defp decode_class(<<top_bit::1, 255::15, data::binary>>), do: {:ok, top_bit == 1, :*, data} |
| 185 | + defp decode_class(_), do: :error |
| 186 | + |
| 187 | + defp decode_ttl(<<ttl::32, data::binary>>), do: {:ok, ttl, data} |
| 188 | + defp decode_ttl(_), do: :error |
| 189 | + |
| 190 | + # leave rdata interpretation to the user |
| 191 | + defp decode_rdata(<<rdlen::16, rdata::binary-size(rdlen), data::binary>>), |
| 192 | + do: {:ok, rdata, data} |
| 193 | + |
| 194 | + defp decode_rdata(_), do: :error |
| 195 | + |
| 196 | + defp encode_header(msg) do |
| 197 | + qr = to_int(msg.qr) |
| 198 | + aa = to_int(msg.aa) |
| 199 | + tc = to_int(msg.tc) |
| 200 | + rd = to_int(msg.rd) |
| 201 | + ra = to_int(msg.ra) |
| 202 | + |
| 203 | + qdcount = length(msg.question) |
| 204 | + ancount = length(msg.answer) |
| 205 | + nscount = length(msg.authority) |
| 206 | + arcount = length(msg.additional) |
| 207 | + |
| 208 | + <<msg.id::16, qr::1, msg.opcode::4, aa::1, tc::1, rd::1, ra::1, msg.z::3, msg.rcode::4, |
| 209 | + qdcount::16, ancount::16, nscount::16, arcount::16>> |
| 210 | + end |
| 211 | + |
| 212 | + defp encode_body(msg) do |
| 213 | + encode_query_section(msg.question) <> |
| 214 | + encode_rr_section(msg.answer) <> |
| 215 | + encode_rr_section(msg.authority) <> |
| 216 | + encode_rr_section(msg.additional) |
| 217 | + end |
| 218 | + |
| 219 | + defp encode_query_section(queries, acc \\ <<>>) |
| 220 | + defp encode_query_section([], acc), do: acc |
| 221 | + |
| 222 | + defp encode_query_section([query | queries], acc) do |
| 223 | + name = encode_name(query.qname) |
| 224 | + type = encode_type(query.qtype) |
| 225 | + class = encode_class(query.qclass, query.unicast_response) |
| 226 | + |
| 227 | + acc = acc <> <<name::binary, type::binary, class::binary>> |
| 228 | + encode_query_section(queries, acc) |
| 229 | + end |
| 230 | + |
| 231 | + defp encode_rr_section(rr, acc \\ <<>>) |
| 232 | + defp encode_rr_section([], acc), do: acc |
| 233 | + |
| 234 | + defp encode_rr_section([rr | rrs], acc) do |
| 235 | + name = encode_name(rr.name) |
| 236 | + type = encode_type(rr.type) |
| 237 | + class = encode_class(rr.class, rr.flush_cache) |
| 238 | + ttl = <<rr.ttl::32>> |
| 239 | + rdlen = <<byte_size(rr.rdata)::16>> |
| 240 | + |
| 241 | + encoded_rr = |
| 242 | + <<name::binary, type::binary, class::binary, ttl::binary, rdlen::binary, rr.rdata::binary>> |
| 243 | + |
| 244 | + acc = acc <> encoded_rr |
| 245 | + encode_rr_section(rrs, acc) |
| 246 | + end |
| 247 | + |
| 248 | + defp encode_name(name) do |
| 249 | + for label <- String.split(name, "."), into: <<>> do |
| 250 | + size = byte_size(label) |
| 251 | + if size > 63, do: raise("Label #{label} too long. Max length: 63.") |
| 252 | + <<size, label::binary>> |
| 253 | + end <> <<0>> |
| 254 | + end |
| 255 | + |
| 256 | + defp encode_type(:a), do: <<1::16>> |
| 257 | + |
| 258 | + defp encode_class(class, top_bit_set) when is_boolean(top_bit_set), |
| 259 | + do: encode_class(class, to_int(top_bit_set)) |
| 260 | + |
| 261 | + defp encode_class(:in, top_bit), do: <<top_bit::1, 1::15>> |
| 262 | + |
| 263 | + defp to_int(true), do: 1 |
| 264 | + defp to_int(false), do: 0 |
| 265 | +end |
0 commit comments