%% ------------------------------------------------------------------- %% %% Copyright (c) 2010 Andrew Tunnell-Jones. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(dns). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(""" The `dns` module is the primary entry point for the functionality in this library. The module exports various types used in type specs, such as `t:message/0`, which indicates a `#dns_message{}` record, `t:query/0` which represents a single `#dns_query{}` record, `t:questions/0`, which represents a list of queries, etc. It also exports functions for encoding and decoding messages, TSIG supporting functions, and various utility functions for comparing domain names, converting domain names into different cases, converting to and from label lists, etc. """). -export([decode_message/1, encode_message/1, encode_message/2]). -export([verify_tsig/3, verify_tsig/4]). -export([add_tsig/5, add_tsig/6]). -export([compare_dname/2]). -export([dname_to_upper/1, dname_to_lower/1]). -export([dname_to_labels/1, labels_to_dname/1, escape_label/1]). -export([unix_time/0, unix_time/1]). -export([random_id/0]). -export([ class_name/1, type_name/1, rcode_name/1, opcode_name/1, tsigerr_name/1, ercode_name/1, eoptcode_name/1, llqopcode_name/1, llqerrcode_name/1, alg_name/1 ]). -export([const_compare/2]). %% Private -export([encode_rrdata/2, decode_rrdata/3]). -export([encode_dname/1]). -ifdef(TEST). -export([encode_rrdata/4, decode_rrdata/4]). -export([encode_svcb_svc_params/1, decode_svcb_svc_params/1]). -export([encode_optrrdata/1, decode_optrrdata/1]). -export([encode_dname/3, encode_dname/4, decode_dname/2]). -export([new_compmap/0]). -endif. -include("dns.hrl"). %% 2^31 - 1, the largest signed 32-bit integer value -define(DEFAULT_TSIG_FUDGE, 5 * 60). -define(MAX_INT32, ((1 bsl 31) - 1)). -define(HEADER_SIZE, 12). %% Minimal size of an OptRR record without any data -define(OPTRR_MIN_SIZE, 88). %% Types -type uint2() :: 0..1. -type uint4() :: 0..15. -type uint8() :: 0..((1 bsl 8) - 1). -type uint16() :: 0..((1 bsl 16) - 1). -type uint32() :: 0..((1 bsl 32) - 1). -type uint48() :: 0..((1 bsl 48) - 1). -type uint64() :: 0..((1 bsl 64) - 1). -export_type([uint2/0, uint4/0, uint8/0, uint16/0, uint32/0, uint48/0, uint64/0]). -export_type([ message/0, message_id/0, message_bin/0, opcode/0, rcode/0, query/0, questions/0, rr/0, optrr/0, answers/0, authority/0, additional/0, dname/0, class/0, type/0, ttl/0, alg/0, label/0, tsig_mac/0, tsig_error/0, decode_error/0, ercode/0, eoptcode/0, llqopcode/0, llqerrcode/0, tsig_opt/0, encode_message_opt/0, rrdata/0, tsig_alg/0, compmap/0, optrr_elem/0, unix_time/0 ]). -export_type([ opt_nsid/0, opt_ul/0, opt_ecs/0, opt_llq/0, opt_owner/0, opt_unknown/0, rrdata_rrsig/0, svcb_svc_params/0 ]). -type decode_error() :: formerr | truncated | trailing_garbage. -type message_bin() :: <<_:64, _:_*8>>. -type message_id() :: uint16(). -type opcode() :: uint4(). -type rcode() :: uint4(). -type questions() :: [query()]. -type records() :: additional() | answers() | authority() | questions(). -type optrr_elem() :: opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner(). -opaque compmap() :: #{[binary()] => non_neg_integer()}. -type message() :: #dns_message{}. -type query() :: #dns_query{}. -type rr() :: #dns_rr{}. -type optrr() :: #dns_optrr{}. -type opt_nsid() :: #dns_opt_nsid{}. -type opt_ul() :: #dns_opt_ul{}. -type opt_ecs() :: #dns_opt_ecs{}. -type opt_llq() :: #dns_opt_llq{}. -type opt_owner() :: #dns_opt_owner{}. -type opt_unknown() :: #dns_opt_unknown{}. -type rrdata_rrsig() :: #dns_rrdata_rrsig{}. -type svcb_svc_params() :: #{ 1..6 => none | char() | binary() }. -type answers() :: [rr()]. -type authority() :: [rr()]. -type additional() :: [optrr() | rr()]. -type dname() :: binary(). -type label() :: binary(). -type class() :: uint16(). -type type() :: uint16(). -type ttl() :: 0..?MAX_INT32. -type rrdata() :: binary() | #dns_rrdata_a{} | #dns_rrdata_aaaa{} | #dns_rrdata_afsdb{} | #dns_rrdata_caa{} | #dns_rrdata_cdnskey{} | #dns_rrdata_cds{} | #dns_rrdata_cert{} | #dns_rrdata_cname{} | #dns_rrdata_dhcid{} | #dns_rrdata_dlv{} | #dns_rrdata_dname{} | #dns_rrdata_dnskey{} | #dns_rrdata_ds{} | #dns_rrdata_hinfo{} | #dns_rrdata_ipseckey{} | #dns_rrdata_key{} | #dns_rrdata_kx{} | #dns_rrdata_loc{} | #dns_rrdata_mb{} | #dns_rrdata_mg{} | #dns_rrdata_minfo{} | #dns_rrdata_mr{} | #dns_rrdata_mx{} | #dns_rrdata_naptr{} | #dns_rrdata_ns{} | #dns_rrdata_nsec{} | #dns_rrdata_nsec3{} | #dns_rrdata_nsec3param{} | #dns_rrdata_nxt{} | #dns_rrdata_ptr{} | #dns_rrdata_rp{} | #dns_rrdata_rrsig{} | #dns_rrdata_rt{} | #dns_rrdata_soa{} | #dns_rrdata_spf{} | #dns_rrdata_srv{} | #dns_rrdata_svcb{} | #dns_rrdata_sshfp{} | #dns_rrdata_tsig{} | #dns_rrdata_txt{}. -type encode_message_opt() :: {max_size, 512..65535} | {tc_mode, default | axfr | llq_event} | {tsig, [encode_message_tsig_opt()]}. -type encode_message_tsig_opt() :: {msgid, message_id()} | {alg, tsig_alg()} | {name, dname()} | {secret, binary()} | {errcode, tsig_error()} | {other, binary()} | tsig_opt(). -type unix_time() :: 0..4294967295. -type tsig_mac() :: binary(). -type tsig_error() :: 0 | 16..18. -type tsig_opt() :: {time, unix_time()} | {fudge, non_neg_integer()} | {mac, tsig_mac()} | {tail, boolean()}. -type tsig_alg() :: binary(). -type alg() :: ?DNS_ALG_DSA | ?DNS_ALG_NSEC3DSA | ?DNS_ALG_RSASHA1 | ?DNS_ALG_NSEC3RSASHA1 | ?DNS_ALG_RSASHA256 | ?DNS_ALG_RSASHA512. -type eoptcode() :: 0..65535. -type ercode() :: 0 | 16. -type llqerrcode() :: 0..6. -type llqopcode() :: 1..3. %%%=================================================================== %%% Message body functions %%%=================================================================== %% @doc Decode a binary DNS message. -spec decode_message(message_bin()) -> {decode_error(), message() | undefined, binary()} | message(). decode_message( <> = MsgBin ) -> try #dns_message{ id = Id, qr = decode_bool(QR), oc = OC, aa = decode_bool(AA), tc = decode_bool(TC), rd = decode_bool(RD), ra = decode_bool(RA), ad = decode_bool(AD), cd = decode_bool(CD), rc = RC, qc = QC, anc = ANC, auc = AUC, adc = ADC } of #dns_message{} = Msg -> decode_message(questions, MsgBin, Rest, Msg) catch _ -> {formerr, undefined, MsgBin} end; decode_message(<<_/binary>> = MsgBin) -> {formerr, undefined, MsgBin}. -spec decode_message( additional | answers | authority | finished | questions, <<_:64, _:_*8>>, binary(), message() ) -> {atom(), message(), binary()} | message(). decode_message(questions, MsgBin, QBody, #dns_message{qc = QC} = Msg) -> case decode_message_questions(QBody, QC, MsgBin) of {Questions, Rest} -> NewMsg = Msg#dns_message{questions = Questions}, decode_message(answers, MsgBin, Rest, NewMsg); {Error, Questions, Rest} -> NewMsg = Msg#dns_message{questions = Questions}, {Error, NewMsg, Rest} end; decode_message( Section, MsgBin, Body, #dns_message{anc = ANC, auc = AUC, adc = ADC} = Msg ) when Section =:= answers orelse Section =:= authority orelse Section =:= additional -> {C, Next} = case Section of answers -> {ANC, authority}; authority -> {AUC, additional}; additional -> {ADC, finished} end, case decode_message_body(Body, C, MsgBin) of {RR, Rest} -> NewMsg = add_rr_to_section(Section, Msg, RR), decode_message(Next, MsgBin, Rest, NewMsg); {Error, RR, Rest} -> NewMsg = add_rr_to_section(Section, Msg, RR), {Error, NewMsg, Rest} end; decode_message(finished, _MsgBin, <<>>, #dns_message{} = Msg) -> Msg; decode_message(finished, _MsgBin, Bin, #dns_message{} = Msg) when is_binary(Bin) -> {trailing_garbage, Msg, Bin}. -spec decode_message_questions(binary(), char(), message_bin()) -> {questions(), binary()} | {decode_error(), questions(), binary()}. decode_message_questions(DataBin, Count, MsgBin) -> decode_message_questions(DataBin, Count, MsgBin, []). -spec decode_message_questions(binary(), char(), message_bin(), questions()) -> {questions(), binary()} | {decode_error(), questions(), binary()}. decode_message_questions(DataBin, 0, _MsgBin, Qs) -> {lists:reverse(Qs), DataBin}; decode_message_questions(<<>>, _Count, _MsgBin, Qs) -> {truncated, lists:reverse(Qs), <<>>}; decode_message_questions(DataBin, Count, MsgBin, Qs) -> try decode_dname(DataBin, MsgBin) of {Name, <>} -> Q = #dns_query{name = Name, type = Type, class = Class}, decode_message_questions(RB, Count - 1, MsgBin, [Q | Qs]); {_Name, _Bin} -> {truncated, lists:reverse(Qs), DataBin} catch Error when is_atom(Error) -> {Error, lists:reverse(Qs), DataBin}; _:_ -> {formerr, lists:reverse(Qs), DataBin} end. -spec decode_message_body(binary(), integer(), <<_:64, _:_*8>>) -> {[{_, _, _, _, _, _}], binary()} | {atom(), [{_, _, _, _, _, _}], binary()}. decode_message_body(DataBin, Count, MsgBin) -> decode_message_body(DataBin, Count, MsgBin, []). -spec decode_message_body(binary(), integer(), <<_:64, _:_*8>>, [optrr() | rr()]) -> {[{_, _, _, _, _, _}], binary()} | {atom(), [{_, _, _, _, _, _}], binary()}. decode_message_body(<<>>, _Count, _MsgBin, RRs) -> {lists:reverse(RRs), <<>>}; decode_message_body(DataBin, 0, _MsgBin, RRs) -> {lists:reverse(RRs), DataBin}; decode_message_body(DataBin, Count, MsgBin, RRs) -> try decode_dname(DataBin, MsgBin) of {<<>>, <>} -> Data = decode_optrrdata(EDataBin), RR = #dns_optrr{ udp_payload_size = UPS, ext_rcode = ExtRcode, version = Version, dnssec = decode_bool(DNSSEC), data = Data }, decode_message_body(RemBin, Count - 1, MsgBin, [RR | RRs]); {Name, <>} -> RR = #dns_rr{ name = Name, type = Type, class = Class, ttl = TTL, data = decode_rrdata(Class, Type, RdataBin, MsgBin) }, decode_message_body(RemBin, Count - 1, MsgBin, [RR | RRs]); {_Name, <<_Type:16/unsigned, _Class:16/unsigned, _TTL:32/signed, Len:16, Data/binary>>} when byte_size(Data) < Len -> {truncated, lists:reverse(RRs), DataBin} catch Error when is_atom(Error) -> {Error, lists:reverse(RRs), DataBin}; _:_ -> {formerr, lists:reverse(RRs), DataBin} end. -spec add_rr_to_section(additional | answers | authority, message(), [optrr() | rr()]) -> message(). add_rr_to_section(answers, #dns_message{} = Msg, RR) -> Msg#dns_message{answers = RR}; add_rr_to_section(authority, #dns_message{} = Msg, RR) -> Msg#dns_message{authority = RR}; add_rr_to_section(additional, #dns_message{} = Msg, RR) -> Msg#dns_message{additional = RR}. %% @doc Encode a dns_message record. -spec encode_message(message()) -> message_bin(). encode_message( #dns_message{ questions = Questions, answers = Answers, authority = Authority, additional = Additional } = Msg ) -> Head = encode_message_head(Msg), Fun = fun(Rec, {CompMapTmp, BinTmp}) -> {CompMapTmp0, RecBin} = encode_message_rec(CompMapTmp, byte_size(BinTmp), Rec), {CompMapTmp0, <>} end, {CompMap0, QBin} = lists:foldl(Fun, {new_compmap(), Head}, Questions), {CompMap1, AnBin} = lists:foldl(Fun, {CompMap0, QBin}, Answers), {CompMap2, AuBin} = lists:foldl(Fun, {CompMap1, AnBin}, Authority), {_CompMap3, AdBin} = lists:foldl(Fun, {CompMap2, AuBin}, Additional), AdBin. %% @doc Encode a dns_message record - will truncate the message as needed. -spec encode_message(message(), [encode_message_opt()]) -> {false, message_bin()} | {true, message_bin(), message()} | {false, message_bin(), tsig_mac()} | {true, message_bin(), tsig_mac(), message()}. encode_message(#dns_message{id = MsgId, additional = Additional} = Msg, Opts) -> EncodeFun = get_tc_mode_fun(Opts), MaxSize = get_max_size(Opts, Additional), case proplists:get_value(tsig, Opts) of undefined -> case EncodeFun(Msg, MaxSize) of {Bin, Leftover} -> {true, Bin, Leftover}; Bin -> {false, Bin} end; TSIGOpts when is_list(TSIGOpts) -> OrigMsgId = proplists:get_value(msgid, TSIGOpts, MsgId), Alg = proplists:get_value(alg, TSIGOpts), Name = proplists:get_value(name, TSIGOpts), Secret = proplists:get_value(secret, TSIGOpts), Err = proplists:get_value(errcode, TSIGOpts, ?DNS_TSIGERR_NOERROR), Time = proplists:get_value(time, TSIGOpts, unix_time()), Fudge = proplists:get_value(fudge, TSIGOpts, ?DEFAULT_TSIG_FUDGE), PreviousMAC = proplists:get_value(mac, TSIGOpts, <<>>), Other = proplists:get_value(other, TSIGOpts, <<>>), Tail = proplists:get_bool(tail, TSIGOpts), TSIGSize = encode_message_tsig_size(Name, Alg, Other), Msg0 = Msg#dns_message{id = OrigMsgId}, case EncodeFun(Msg0, MaxSize - TSIGSize) of {MsgBin, MsgLeftover} -> MsgLeftover0 = MsgLeftover#dns_message{id = MsgId}, {MsgBin0, NewMAC} = encode_message_tsig_add( MsgId, Name, Alg, Secret, Time, Fudge, Err, Other, PreviousMAC, Tail, MsgBin ), {true, MsgBin0, NewMAC, MsgLeftover0}; MsgBin -> {MsgBin0, NewMAC} = encode_message_tsig_add( MsgId, Name, Alg, Secret, Time, Fudge, Err, Other, PreviousMAC, Tail, MsgBin ), {false, MsgBin0, NewMAC} end end. -spec get_tc_mode_fun(proplists:proplist()) -> fun((message(), number()) -> message_bin() | {message_bin(), message()}). get_tc_mode_fun(Opts) -> case proplists:get_value(tc_mode, Opts, default) of default -> fun encode_message_default/2; llq_event -> fun encode_message_llq/2; axfr -> fun encode_message_axfr/2; _ -> erlang:error(badarg) end. -spec get_max_size(proplists:proplist(), _) -> 512..65535. get_max_size(Opts, Additional) -> case {proplists:get_value(max_size, Opts), Additional} of {MaxSize, _} when is_integer(MaxSize) andalso 512 =< MaxSize andalso MaxSize =< 65535 -> MaxSize; {undefined, [#dns_optrr{udp_payload_size = MaxSize}]} when is_integer(MaxSize) andalso 512 =< MaxSize andalso MaxSize =< 65535 -> MaxSize; {undefined, []} -> 512; _ -> erlang:error(badarg) end. -spec encode_message_tsig_add( 1..1114111, binary(), <<_:64, _:_*8>>, _, integer(), integer(), integer(), binary(), binary(), boolean(), <<_:64, _:_*8>> ) -> {<<_:32, _:_*8>>, binary()}. encode_message_tsig_add( MsgId, Name, Alg, Secret, Time, Fudge, Err, Other, PMAC, Tail, <> = MsgBin ) -> case gen_tsig_mac( Alg, MsgBin, Name, Secret, Time, Fudge, Err, Other, PMAC, Tail ) of {ok, MAC} -> MS = byte_size(MAC), OLen = byte_size(Other), NameBin = encode_dname(Name), AlgBin = encode_dname(Alg), TSIGData = <>, TSIGDataSize = byte_size(TSIGData), TSigRR = <>, MsgBin0 = <>, {MsgBin0, MAC}; {error, _} -> erlang:error(badarg) end. -spec encode_message_tsig_size(binary(), <<_:64, _:_*8>>, bitstring()) -> pos_integer(). encode_message_tsig_size(Name, Alg, Other) -> NameSize = byte_size(encode_dname(Name)), AlgSize = byte_size(encode_dname(Alg)), MACSize = case Alg of ?DNS_TSIG_ALG_MD5 -> 16; ?DNS_TSIG_ALG_SHA1 -> 20; ?DNS_TSIG_ALG_SHA224 -> 28; ?DNS_TSIG_ALG_SHA256 -> 32; ?DNS_TSIG_ALG_SHA384 -> 48; ?DNS_TSIG_ALG_SHA512 -> 64 end, OtherSize = byte_size(Other), DataSize = AlgSize + 16 + MACSize + OtherSize, NameSize + 10 + DataSize. -spec encode_message_default(message(), number()) -> binary(). encode_message_default(#dns_message{additional = Additional} = Msg, MaxSize) -> %% If EDNS0 is used, we need to reserve space for appending the OptRR record at its minimal PreservedOptRRBinSize = preserve_optrr_size(Additional), SpaceLeft = MaxSize - ?HEADER_SIZE - PreservedOptRRBinSize, case encode_message_d_req(Msg, SpaceLeft) of {false, QC, ANC, AUC, Body} -> BodySize = byte_size(Body), %% We ran out of space, we MUST append a OptRR EDNS0 record, %% and this takes precedence over the body %% Note however that according to RFC6891 §7, the question section MUST be present OptRRBinMin = ensure_optrr(Additional, minimal), OptRRBinSizeMin = byte_size(OptRRBinMin), OptRRBinFull = ensure_optrr(Additional, full), OptRRBinSizeFull = byte_size(OptRRBinFull), Head = build_head(Msg, true, QC, ANC, AUC, 1), SpaceForOptRR = SpaceLeft - BodySize, case {OptRRBinSizeFull =< SpaceForOptRR, OptRRBinSizeMin =< SpaceForOptRR} of {false, true} -> <>; {true, _} -> <> end; {CompMap, QC, ANC, AUC, Body} -> BodySize = byte_size(Body), {OptRRBin, Ad0} = encode_message_pop_optrr(Additional), OptRRBinSize = byte_size(OptRRBin), Pos0 = BodySize + ?HEADER_SIZE, case SpaceLeft - BodySize of SpaceLeft0 when SpaceLeft0 < OptRRBinSize -> Head = build_head(Msg, true, QC, ANC, AUC, 0), <>; SpaceLeft0 -> Pos1 = Pos0 + OptRRBinSize, SpaceLeft1 = SpaceLeft0 - OptRRBinSize, OptC = case OptRRBinSize of 0 -> 0; _ -> 1 end, case encode_message_d_opt(Pos1, SpaceLeft1, CompMap, Ad0) of false -> Head = build_head(Msg, false, QC, ANC, AUC, OptC), <>; {ADC, AdBin} -> Head = build_head(Msg, false, QC, ANC, AUC, OptC + ADC), <> end end end. -spec build_head(message(), boolean(), uint16(), uint16(), uint16(), uint16()) -> message_bin(). build_head(#dns_message{tc = TC} = Msg, TCBool, EncQC, EncANC, EncAUC, EncADC) -> Msg0 = Msg#dns_message{ qc = EncQC, anc = EncANC, auc = EncAUC, adc = EncADC, tc = TC orelse TCBool }, encode_message_head(Msg0). %% Encodes questions, authorities, and answers, for as long as there is space -spec encode_message_d_req(message(), integer()) -> {false | compmap(), uint16(), uint16(), uint16(), bitstring()}. encode_message_d_req(Msg, SpaceLeft) -> Msg0 = Msg#dns_message{qc = 0, anc = 0, auc = 0}, encode_message_d_req(Msg0, ?HEADER_SIZE, SpaceLeft, new_compmap(), <<>>). -spec encode_message_d_req(message(), pos_integer(), integer(), compmap(), bitstring()) -> {false | compmap(), uint16(), uint16(), uint16(), bitstring()}. encode_message_d_req( #dns_message{qc = QC, anc = ANC, auc = AUC} = Msg, Pos, SpaceLeft, CompMap, Bin ) -> case encode_message_pop(Msg) of {additional, _} -> {CompMap, QC, ANC, AUC, Bin}; {Section, Recs} -> RecsLen = length(Recs), {CompMap0, NewBin, Recs0} = encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs), Recs0Len = length(Recs0), EncodedLen = RecsLen - Recs0Len, Msg1 = encode_message_put(Msg, Recs0, EncodedLen, Section), Bin0 = <>, case Recs0Len of 0 -> NewBinSize = byte_size(NewBin), Pos0 = Pos + NewBinSize, SpaceLeft0 = SpaceLeft - NewBinSize, encode_message_d_req(Msg1, Pos0, SpaceLeft0, CompMap0, Bin0); _ -> #dns_message{qc = QC0, anc = ANC0, auc = AUC0} = Msg1, {false, QC0, ANC0, AUC0, Bin0} end end. -spec encode_message_d_opt( pos_integer(), number(), compmap(), records() ) -> false | {non_neg_integer(), bitstring()}. encode_message_d_opt(Pos, SpaceLeft, CompMap, Recs) -> case encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs) of {_, Bin, []} -> {length(Recs), Bin}; _ -> false end. -spec encode_message_axfr(message(), number()) -> binary() | {binary(), message()}. encode_message_axfr(#dns_message{} = Msg, MaxSize) -> Pos = ?HEADER_SIZE, SpaceLeft = MaxSize - Pos, encode_message_axfr(Msg, Pos, SpaceLeft, new_compmap(), <<>>). -spec encode_message_axfr(message(), pos_integer(), number(), compmap(), binary()) -> binary() | {binary(), message()}. encode_message_axfr(Msg, Pos, SpaceLeft, CompMap, Bin) -> {Section, Recs} = encode_message_pop(Msg), RecsLen = length(Recs), {CompMap0, NewBin, Recs0} = encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs), Recs0Len = length(Recs0), EncodedLen = RecsLen - Recs0Len, Msg1 = encode_message_put(Msg, Recs0, EncodedLen, Section), case Recs0Len of 0 when Section =:= additional -> Head = encode_message_head(Msg1), <>; 0 -> NewBinSize = byte_size(NewBin), Pos0 = Pos + NewBinSize, SpaceLeft0 = SpaceLeft - NewBinSize, Bin0 = <>, encode_message_axfr(Msg1, Pos0, SpaceLeft0, CompMap0, Bin0); _ -> Head = encode_message_head(Msg1), Msg2 = encode_message_a_setcounts(Msg1), {<>, Msg2} end. -spec encode_message_pop(message()) -> {additional, dns:additional()} | {answers, dns:answers()} | {authority, dns:authority()} | {questions, [query()]}. encode_message_pop(#dns_message{questions = [_ | _] = Recs}) -> {questions, Recs}; encode_message_pop(#dns_message{answers = [_ | _] = Recs}) -> {answers, Recs}; encode_message_pop(#dns_message{authority = [_ | _] = Recs}) -> {authority, Recs}; encode_message_pop(#dns_message{additional = Recs}) -> {additional, Recs}. -spec encode_message_put (message(), questions(), uint16(), questions) -> message(); (message(), answers(), uint16(), answers) -> message(); (message(), authority(), uint16(), authority) -> message(); (message(), additional(), uint16(), additional) -> message(). encode_message_put(Msg, Recs, Count, questions) -> Msg#dns_message{qc = Count, questions = Recs}; encode_message_put(Msg, Recs, Count, answers) -> Msg#dns_message{anc = Count, answers = Recs}; encode_message_put(Msg, Recs, Count, authority) -> Msg#dns_message{auc = Count, authority = Recs}; encode_message_put(Msg, Recs, Count, additional) -> Msg#dns_message{adc = Count, additional = Recs}. -spec encode_message_a_setcounts(message()) -> message(). encode_message_a_setcounts( #dns_message{ questions = Q, answers = Answers, authority = Authority, additional = Additional } = Msg ) -> Msg#dns_message{ qc = length(Q), anc = length(Answers), auc = length(Authority), adc = length(Additional) }. -spec encode_message_head(message()) -> <<_:96>>. encode_message_head(#dns_message{ id = Id, qr = QR, oc = OC, aa = AA, tc = TC, rd = RD, ra = RA, ad = AD, cd = CD, rc = RC, qc = QC, anc = ANC, auc = AUC, adc = ADC }) -> <>. -spec encode_message_llq(message(), number()) -> binary() | {binary(), message()}. encode_message_llq( #dns_message{ questions = Q, answers = Answers, authority = Authority, additional = Additional } = Msg, MaxSize ) -> QC = length(Q), AnswersLen = length(Answers), AuthorityLen = length(Authority), AdditionalLen = length(Additional), AuAd = Authority ++ Additional, Pos = ?HEADER_SIZE, SpaceLeft = MaxSize - Pos, {CompMap0, QBin, []} = encode_message_rec_list(Pos, SpaceLeft, new_compmap(), Q), QBinSize = byte_size(QBin), SpaceLeft0 = SpaceLeft - QBinSize, Pos0 = QBinSize + Pos, {_, AuAdTmp, []} = encode_message_rec_list(Pos0, SpaceLeft0, CompMap0, AuAd), AuAdTmpSize = byte_size(AuAdTmp), {CompMap1, AnBin, LeftoverAn} = encode_message_rec_list(Pos0, SpaceLeft0 - AuAdTmpSize, CompMap0, Answers), LeftoverAnC = length(LeftoverAn), EncodedAnC = AnswersLen - LeftoverAnC, AnBinSize = byte_size(AnBin), Pos1 = Pos0 + AnBinSize, SpaceLeft1 = SpaceLeft0 - AnBinSize, {_, AuAdBin, []} = encode_message_rec_list(Pos1, SpaceLeft1, CompMap1, AuAd), Msg0 = Msg#dns_message{qc = QC, anc = EncodedAnC, auc = AuthorityLen, adc = AdditionalLen}, Head = encode_message_head(Msg0), Bin = <>, case LeftoverAnC =:= 0 of true -> Bin; false -> {Bin, Msg#dns_message{anc = LeftoverAnC, answers = LeftoverAn}} end. -spec encode_message_rec_list( pos_integer(), number(), compmap(), records() ) -> {compmap(), bitstring(), records()}. encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs) -> encode_message_rec_list(Pos, SpaceLeft, CompMap, <<>>, Recs). -spec encode_message_rec_list( pos_integer(), number(), _, bitstring(), records() ) -> {compmap(), bitstring(), records()}. encode_message_rec_list(Pos, SpaceLeft, CompMap, Body, [Rec | Rest] = Recs) -> {CompMap0, NewBin} = encode_message_rec(CompMap, Pos, Rec), NewBinSize = byte_size(NewBin), case SpaceLeft - NewBinSize of SpaceLeft0 when SpaceLeft0 > 0 -> Pos0 = Pos + NewBinSize, Body0 = <>, encode_message_rec_list(Pos0, SpaceLeft0, CompMap0, Body0, Rest); _ -> {CompMap, Body, Recs} end; encode_message_rec_list(_Pos, _SpaceLeft, CompMap, Body, []) -> {CompMap, Body, []}. -spec encode_message_rec(compmap(), non_neg_integer(), query() | optrr() | rr()) -> {compmap(), <<_:32, _:_*8>>}. encode_message_rec(CompMap, Pos, #dns_query{name = N, type = T, class = C}) -> {NameBin, CompMap0} = encode_dname(CompMap, Pos, N), {CompMap0, <>}; encode_message_rec(CompMap, _Pos, #dns_optrr{} = OptRR) -> {CompMap, encode_optrr(OptRR)}; encode_message_rec(CompMap, Pos, #dns_rr{ name = N, type = T, class = C, ttl = TTL, data = D }) -> {NameBin, CompMap0} = encode_dname(CompMap, Pos, N), DPos = Pos + byte_size(NameBin) + 2 + 2 + 4 + 2, {DBin, CompMap1} = encode_rrdata(DPos, C, D, CompMap0), DSize = byte_size(DBin), {CompMap1, <>}. -spec encode_message_pop_optrr(additional()) -> {binary(), additional()}. encode_message_pop_optrr([#dns_optrr{} = OptRR | Rest]) -> {encode_optrr(OptRR), Rest}; encode_message_pop_optrr(Other) -> {<<>>, Other}. -spec ensure_optrr(additional(), minimal | full) -> binary(). ensure_optrr([#dns_optrr{} = OptRR | _], full) -> encode_optrr(OptRR); ensure_optrr([#dns_optrr{} = OptRR | _], minimal) -> encode_optrr(OptRR#dns_optrr{data = []}); ensure_optrr(_, _) -> <<>>. -spec preserve_optrr_size(additional()) -> non_neg_integer(). preserve_optrr_size([#dns_optrr{} | _]) -> ?OPTRR_MIN_SIZE; preserve_optrr_size(_) -> 0. -spec encode_optrr(optrr()) -> binary(). encode_optrr(#dns_optrr{ udp_payload_size = UPS, ext_rcode = ExtRcode0, version = Version0, dnssec = DNSSEC, data = Data }) -> %% TODO: if returning BADVERS, we want to avoid returning any answer in the top #dns_message{} {Version, ExtRcode} = ensure_edns_version(Version0, ExtRcode0), DNSSECBit = encode_bool(DNSSEC), RRBin = encode_optrrdata(Data), RRBinSize = byte_size(RRBin), <<0, ?DNS_TYPE_OPT:16, UPS:16, ExtRcode:8, Version:8, DNSSECBit:1, 0:15, RRBinSize:16, RRBin/binary>>. ensure_edns_version(Version, ExtRcode) when ?DNS_EDNS_MIN_VERSION =< Version andalso Version =< ?DNS_EDNS_MAX_VERSION -> {Version, ExtRcode}; ensure_edns_version(_, _) -> {?DNS_EDNS_MAX_VERSION, ?DNS_ERCODE_BADVERS_NUMBER}. %% @doc Returns a random integer suitable for use as DNS message identifier. -spec random_id() -> message_id(). random_id() -> rand:uniform(65535). %%%=================================================================== %%% TSIG functions %%%=================================================================== %% @equiv verify_tsig(MsgBin, Name, Secret, []) -spec verify_tsig(message_bin(), dname(), binary()) -> {ok, tsig_mac()} | {error, tsig_error()}. verify_tsig(MsgBin, Name, Secret) -> verify_tsig(MsgBin, Name, Secret, []). %% @doc Verifies a TSIG message signature. -spec verify_tsig(message_bin(), dname(), binary(), [tsig_opt()]) -> {ok, tsig_mac()} | {error, tsig_error()}. verify_tsig(MsgBin, Name, Secret, Options) -> {UnsignedMsgBin, #dns_rr{name = TName, data = TData}} = strip_tsig(MsgBin), case compare_dname(Name, TName) of true -> do_verify_tsig(UnsignedMsgBin, TData, Name, Secret, Options); false -> {error, ?DNS_TSIGERR_BADKEY} end. do_verify_tsig(UnsignedMsgBin, TData, Name, Secret, Options) -> Now = proplists:get_value(time, Options, unix_time()), Fudge = proplists:get_value(fudge, Options, ?DEFAULT_TSIG_FUDGE), PreviousMAC = proplists:get_value(mac, Options, <<>>), Tail = proplists:get_bool(tail, Options), #dns_rrdata_tsig{ alg = Alg, time = Time, fudge = CFudge, mac = CMAC, err = Err, other = Other } = TData, case gen_tsig_mac( Alg, UnsignedMsgBin, Name, Secret, Time, CFudge, Err, Other, PreviousMAC, Tail ) of {ok, SMAC} -> case const_compare(CMAC, SMAC) of true -> case (Time - Fudge) =< Now andalso Now =< (Time + Fudge) of false -> {error, ?DNS_TSIGERR_BADTIME}; true -> {ok, SMAC} end; false -> {error, ?DNS_TSIGERR_BADSIG} end; {error, Error} -> {error, Error} end. %% @doc Generates and then appends a TSIG RR to a message. %% Supports MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 algorithms. %% @equiv add_tsig(Msg, Alg, Name, Secret, ErrCode, []) -spec add_tsig(message(), tsig_alg(), dname(), binary(), tsig_error()) -> message(). add_tsig(Msg, Alg, Name, Secret, ErrCode) -> add_tsig(Msg, Alg, Name, Secret, ErrCode, []). %% @doc Generates and then appends a TSIG RR to a message. %% Supports MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 algorithms. -spec add_tsig( message(), tsig_alg(), dname(), binary(), tsig_error(), [tsig_opt()] ) -> message(). add_tsig(Msg, Alg, Name, Secret, ErrCode, Options) -> MsgId = Msg#dns_message.id, MsgBin = encode_message(Msg), Time = proplists:get_value(time, Options, unix_time()), Fudge = proplists:get_value(fudge, Options, ?DEFAULT_TSIG_FUDGE), PreviousMAC = proplists:get_value(mac, Options, <<>>), Other = proplists:get_value(other, Options, <<>>), Tail = proplists:get_bool(tail, Options), {ok, MAC} = gen_tsig_mac( Alg, MsgBin, Name, Secret, Time, Fudge, ErrCode, Other, PreviousMAC, Tail ), Data = #dns_rrdata_tsig{ msgid = MsgId, alg = Alg, time = Time, fudge = Fudge, mac = MAC, err = ErrCode, other = Other }, RR = #dns_rr{ name = Name, class = ?DNS_CLASS_ANY, type = ?DNS_TYPE_TSIG, ttl = 0, data = Data }, NewAdditional = Msg#dns_message.additional ++ [RR], NewADC = Msg#dns_message.adc + 1, Msg#dns_message{adc = NewADC, additional = NewAdditional}. -spec strip_tsig(<<_:64, _:_*8>>) -> {<<_:64, _:_*8>>, rr()}. strip_tsig( <<_Id:16, _QR:1, _OC:4, _AA:1, _TC:1, _RD:1, _RA:1, _PR:1, _Z:2, _RC:4, _QC:16, _ANC:16, _AUC:16, ADC:16, _HRB/binary>> ) when ADC =:= 0 -> throw(no_tsig); strip_tsig( <<_Id:16, QR:1, OC:4, AA:1, TC:1, RD:1, RA:1, PR:1, Z:2, RC:4, QC:16, ANC:16, AUC:16, ADC:16, HRB/binary>> = MsgBin ) -> UnsignedADC = ADC - 1, {_Questions, QRB} = decode_message_questions(HRB, QC, MsgBin), {_Answers, TSIGBin} = decode_message_body(QRB, ANC + AUC + UnsignedADC, MsgBin), case decode_message_body(TSIGBin, 1, MsgBin) of {[#dns_rr{data = #dns_rrdata_tsig{msgid = NewId}} = TSigRR], <<>>} -> MsgBodyLen = byte_size(HRB) - byte_size(TSIGBin), {UnsignedBodyBin, TSIGBin} = split_binary(HRB, MsgBodyLen), UnsignedMsgBin = <>, {UnsignedMsgBin, TSigRR}; {[#dns_rr{data = #dns_rrdata_tsig{}}], _} -> throw(trailing_garbage); _ -> throw(no_tsig) end. -spec gen_tsig_mac( binary(), <<_:64, _:_*8>>, binary(), _, integer(), integer(), _, bitstring(), binary(), boolean() ) -> {error, 17} | {ok, _}. gen_tsig_mac(Alg, Msg, Name, Secret, Time, Fudge, Err, Other, MAC, Tail) -> NameBin = encode_dname(dname_to_lower(Name)), AlgBin = encode_dname(dname_to_lower(Alg)), OLen = byte_size(Other), PMAC = case MAC of <<>> -> MAC; _ -> <<(byte_size(MAC)):16, MAC/binary>> end, Data = case Tail of true -> [PMAC, Msg, <>, <>]; false -> [ PMAC, Msg, NameBin, <>, <<0:32>>, AlgBin, <>, <>, <>, <>, Other ] end, case hmac(Alg, Secret, Data) of {ok, _MAC} = Result -> Result; {error, bad_alg} -> {error, ?DNS_TSIGERR_BADKEY} end. -spec hmac(binary(), _, [bitstring(), ...]) -> {error, bad_alg} | {ok, _}. hmac(TypeBin, Key, Data) -> case hmac_type(TypeBin) of undefined -> {error, bad_alg}; TypeAtom -> {ok, crypto:mac(hmac, TypeAtom, Key, Data)} end. -spec hmac_type(binary()) -> md5 | sha | sha224 | sha256 | sha384 | sha512 | undefined. hmac_type(?DNS_TSIG_ALG_MD5) -> md5; hmac_type(?DNS_TSIG_ALG_SHA1) -> sha; hmac_type(?DNS_TSIG_ALG_SHA224) -> sha224; hmac_type(?DNS_TSIG_ALG_SHA256) -> sha256; hmac_type(?DNS_TSIG_ALG_SHA384) -> sha384; hmac_type(?DNS_TSIG_ALG_SHA512) -> sha512; hmac_type(Alg) -> case dname_to_lower(Alg) of Alg -> undefined; AlgLower -> hmac_type(AlgLower) end. %%%=================================================================== %%% Record data functions %%%=================================================================== -define(CLASS_IS_IN(T), (T =:= ?DNS_CLASS_IN orelse T =:= ?DNS_CLASS_NONE)). %% @private -spec decode_rrdata(uint16(), uint16(), binary()) -> rrdata(). decode_rrdata(Class, Type, Data) -> decode_rrdata(Class, Type, Data, <<>>). -spec decode_rrdata(uint16(), uint16(), binary(), binary()) -> rrdata(). decode_rrdata(_Class, _Type, <<>>, _MsgBin) -> <<>>; decode_rrdata(Class, ?DNS_TYPE_A, <>, _MsgBin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_a{ip = {A, B, C, D}}; decode_rrdata( Class, ?DNS_TYPE_AAAA, <>, _MsgBin ) when ?CLASS_IS_IN(Class) -> #dns_rrdata_aaaa{ip = {A, B, C, D, E, F, G, H}}; decode_rrdata(_Class, ?DNS_TYPE_AFSDB, <>, MsgBin) -> #dns_rrdata_afsdb{ subtype = Subtype, hostname = decode_dnameonly(Bin, MsgBin) }; decode_rrdata(_Class, ?DNS_TYPE_CAA, <>, _MsgBin) -> <> = Bin, #dns_rrdata_caa{flags = Flags, tag = Tag, value = Value}; decode_rrdata(_Class, ?DNS_TYPE_CERT, <>, _MsgBin) -> #dns_rrdata_cert{type = Type, key_tag = KeyTag, alg = Alg, cert = Bin}; decode_rrdata(_Class, ?DNS_TYPE_CNAME, Bin, MsgBin) -> #dns_rrdata_cname{dname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(Class, ?DNS_TYPE_DHCID, Bin, _MsgBin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_dhcid{data = Bin}; decode_rrdata(_Class, ?DNS_TYPE_DLV, <>, _MsgBin) -> #dns_rrdata_dlv{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(_Class, ?DNS_TYPE_DNAME, Bin, MsgBin) -> #dns_rrdata_dname{dname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata( _Class, ?DNS_TYPE_DNSKEY, <> = Bin, _MsgBin ) when AlgNum =:= ?DNS_ALG_RSASHA1 orelse AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse AlgNum =:= ?DNS_ALG_RSASHA256 orelse AlgNum =:= ?DNS_ALG_RSASHA512 -> Key = case PublicKey of <<0, Len:16, Exp:Len/unit:8, ModBin/binary>> -> [Exp, binary:decode_unsigned(ModBin)]; <> -> [Exp, binary:decode_unsigned(ModBin)] end, KeyTag = bin_to_key_tag(Bin), #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, key_tag = KeyTag }; decode_rrdata( _Class, ?DNS_TYPE_DNSKEY, <> = Bin, _MsgBin ) when (AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso T =< 8 -> S = 64 + T * 8, <> = KeyBin, Key = [P, Q, G, Y], KeyTag = bin_to_key_tag(Bin), #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, key_tag = KeyTag }; decode_rrdata( _Class, ?DNS_TYPE_DNSKEY, <> = Bin, _MsgBin ) -> #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, key_tag = bin_to_key_tag(Bin) }; decode_rrdata( _Class, ?DNS_TYPE_CDNSKEY, <> = Bin, _MsgBin ) when AlgNum =:= ?DNS_ALG_RSASHA1 orelse AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse AlgNum =:= ?DNS_ALG_RSASHA256 orelse AlgNum =:= ?DNS_ALG_RSASHA512 -> Key = case PublicKey of <<0, Len:16, Exp:Len/unit:8, ModBin/binary>> -> [Exp, binary:decode_unsigned(ModBin)]; <> -> [Exp, binary:decode_unsigned(ModBin)] end, KeyTag = bin_to_key_tag(Bin), #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, key_tag = KeyTag }; decode_rrdata( _Class, ?DNS_TYPE_CDNSKEY, <> = Bin, _MsgBin ) when (AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso T =< 8 -> S = 64 + T * 8, <> = KeyBin, Key = [P, Q, G, Y], KeyTag = bin_to_key_tag(Bin), #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, key_tag = KeyTag }; decode_rrdata( _Class, ?DNS_TYPE_CDNSKEY, <> = Bin, _MsgBin ) -> #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, key_tag = bin_to_key_tag(Bin) }; decode_rrdata(_Class, ?DNS_TYPE_DS, <>, _MsgBin) -> #dns_rrdata_ds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(_Class, ?DNS_TYPE_CDS, <>, _MsgBin) -> #dns_rrdata_cds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(_Class, ?DNS_TYPE_HINFO, Bin, _BodyBin) -> [CPU, OS] = decode_text(Bin), #dns_rrdata_hinfo{cpu = CPU, os = OS}; decode_rrdata( _Class, ?DNS_TYPE_IPSECKEY, <>, _MsgBin ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = <<>>, public_key = PublicKey }; decode_rrdata( _Class, ?DNS_TYPE_IPSECKEY, <>, _MsgBin ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D}, public_key = PublicKey }; decode_rrdata( _Class, ?DNS_TYPE_IPSECKEY, <>, _MsgBin ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D, E, F, G, H}, public_key = PublicKey }; decode_rrdata(_Class, ?DNS_TYPE_IPSECKEY, <>, MsgBin) -> {Gateway, PublicKey} = decode_dname(Bin, MsgBin), #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = Gateway, public_key = PublicKey }; decode_rrdata( _Class, ?DNS_TYPE_KEY, <>, _MsgBin ) -> #dns_rrdata_key{ type = Type, xt = XT, name_type = NamType, sig = Sig, protocol = Protocol, alg = Alg, public_key = PublicKey }; decode_rrdata(_Class, ?DNS_TYPE_KX, <>, MsgBin) -> #dns_rrdata_kx{ preference = Preference, exchange = decode_dnameonly(Bin, MsgBin) }; decode_rrdata( _Class, ?DNS_TYPE_LOC, <<0:8, SizeB:4, SizeE:4, HorizB:4, HorizE:4, VertB:4, VertE:4, LatPre:32, LonPre:32, AltPre:32>>, _MsgBin ) when SizeE < 10 andalso HorizE < 10 andalso VertE < 10 -> #dns_rrdata_loc{ size = SizeB * (round_pow(10, SizeE)), horiz = HorizB * (round_pow(10, HorizE)), vert = VertB * (round_pow(10, VertE)), lat = decode_loc_point(LatPre), lon = decode_loc_point(LonPre), alt = AltPre - 10000000 }; decode_rrdata(_Class, ?DNS_TYPE_MB, Bin, MsgBin) -> #dns_rrdata_mb{madname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_MG, Bin, MsgBin) -> #dns_rrdata_mg{madname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_MINFO, Bin, MsgBin) when is_binary(Bin) -> {RMB, EMB} = decode_dname(Bin, MsgBin), #dns_rrdata_minfo{rmailbx = RMB, emailbx = decode_dnameonly(EMB, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_MR, Bin, MsgBin) -> #dns_rrdata_mr{newname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_MX, <>, MsgBin) -> #dns_rrdata_mx{ preference = Preference, exchange = decode_dnameonly(Bin, MsgBin) }; decode_rrdata( _Class, ?DNS_TYPE_NAPTR, <>, MsgBin ) -> {Bin1, Flags} = decode_string(Bin), {Bin2, Services} = decode_string(Bin1), {Bin3, RawRegexp} = decode_string(Bin2), Regexp = unicode:characters_to_binary(RawRegexp, utf8), #dns_rrdata_naptr{ order = Order, preference = Preference, flags = Flags, services = Services, regexp = Regexp, replacement = decode_dnameonly(Bin3, MsgBin) }; decode_rrdata(_Class, ?DNS_TYPE_NS, Bin, MsgBin) -> #dns_rrdata_ns{dname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_NSEC, Bin, MsgBin) -> {NextDName, TypeBMP} = decode_dname(Bin, MsgBin), Types = decode_nsec_types(TypeBMP), #dns_rrdata_nsec{next_dname = NextDName, types = Types}; decode_rrdata( _Class, ?DNS_TYPE_NSEC3, <>, _MsgBin ) -> #dns_rrdata_nsec3{ hash_alg = HashAlg, opt_out = decode_bool(OptOut), iterations = Iterations, salt = Salt, hash = Hash, types = decode_nsec_types(TypeBMP) }; decode_rrdata( _Class, ?DNS_TYPE_NSEC3PARAM, <>, _MsgBin ) -> #dns_rrdata_nsec3param{ hash_alg = Alg, flags = Flags, iterations = Iterations, salt = Salt }; decode_rrdata(_Class, ?DNS_TYPE_NXT, Bin, MsgBin) -> {NxtDName, BMP} = decode_dname(Bin, MsgBin), #dns_rrdata_nxt{dname = NxtDName, types = decode_nxt_bmp(BMP)}; decode_rrdata(_Class, ?DNS_TYPE_PTR, Bin, MsgBin) -> #dns_rrdata_ptr{dname = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_RP, Bin, MsgBin) -> {Mbox, TxtBin} = decode_dname(Bin, MsgBin), #dns_rrdata_rp{mbox = Mbox, txt = decode_dnameonly(TxtBin, MsgBin)}; decode_rrdata( _Class, ?DNS_TYPE_RRSIG, <>, MsgBin ) -> {SigName, Sig} = decode_dname(Bin, MsgBin), #dns_rrdata_rrsig{ type_covered = Type, alg = Alg, labels = Labels, original_ttl = TTL, expiration = Expire, inception = Inception, key_tag = KeyTag, signers_name = SigName, signature = Sig }; decode_rrdata(_Class, ?DNS_TYPE_RT, <>, MsgBin) -> #dns_rrdata_rt{preference = Pref, host = decode_dnameonly(Bin, MsgBin)}; decode_rrdata(_Class, ?DNS_TYPE_SOA, Bin, MsgBin) -> {MName, RNBin} = decode_dname(Bin, MsgBin), {RName, Rest} = decode_dname(RNBin, MsgBin), <> = Rest, #dns_rrdata_soa{ mname = MName, rname = RName, serial = Ser, refresh = Ref, retry = Ret, expire = Exp, minimum = Min }; decode_rrdata(_Class, ?DNS_TYPE_SPF, Bin, _MsgBin) -> #dns_rrdata_spf{spf = decode_text(Bin)}; decode_rrdata( _Class, ?DNS_TYPE_SRV, <>, MsgBin ) -> #dns_rrdata_srv{ priority = Pri, weight = Wght, port = Port, target = decode_dnameonly(Bin, MsgBin) }; decode_rrdata( _Class, ?DNS_TYPE_SSHFP, <>, _MsgBin ) -> #dns_rrdata_sshfp{alg = Alg, fp_type = FPType, fp = FingerPrint}; decode_rrdata(_Class, ?DNS_TYPE_SVCB, <>, MsgBin) -> {TargetName, SvcParamsBin} = decode_dname(Bin, MsgBin), SvcParams = decode_svcb_svc_params(SvcParamsBin), #dns_rrdata_svcb{svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams}; decode_rrdata(_Class, ?DNS_TYPE_TSIG, Bin, MsgBin) -> {Alg, <>} = decode_dname(Bin, MsgBin), #dns_rrdata_tsig{ alg = Alg, time = Time, fudge = Fudge, mac = MAC, msgid = MsgId, err = ErrInt, other = Other }; decode_rrdata(_Class, ?DNS_TYPE_TXT, Bin, _MsgBin) -> #dns_rrdata_txt{txt = decode_text(Bin)}; decode_rrdata(_Class, _Type, Bin, _MsgBin) -> Bin. %% @private -spec encode_rrdata(_, rrdata()) -> binary(). encode_rrdata(Class, Data) -> {Bin, undefined} = encode_rrdata(0, Class, Data, undefined), Bin. -spec encode_rrdata(non_neg_integer(), _, rrdata(), undefined | compmap()) -> {binary(), undefined | compmap()}. encode_rrdata(_Pos, Class, #dns_rrdata_a{ip = {A, B, C, D}}, CompMap) when ?CLASS_IS_IN(Class) -> {<>, CompMap}; encode_rrdata(_Pos, Class, #dns_rrdata_aaaa{ip = {A, B, C, D, E, F, G, H}}, CompMap) when ?CLASS_IS_IN(Class) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_afsdb{ subtype = Subtype, hostname = Hostname }, CompMap ) -> HostnameBin = encode_dname(Hostname), {<>, CompMap}; encode_rrdata(_Pos, _Class, #dns_rrdata_caa{flags = Flags, tag = Tag, value = Value}, CompMap) -> Len = byte_size(Tag), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_cert{ type = Type, key_tag = KeyTag, alg = Alg, cert = Bin }, CompMap ) -> {<>, CompMap}; encode_rrdata(Pos, _Class, #dns_rrdata_cname{dname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata(_Pos, ?DNS_CLASS_IN, #dns_rrdata_dhcid{data = Bin}, CompMap) -> {Bin, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_dlv{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }, CompMap ) -> {<>, CompMap}; encode_rrdata(Pos, _Class, #dns_rrdata_dname{dname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata( _Pos, _Class, #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = [E, M] }, CompMap ) when Alg =:= ?DNS_ALG_RSASHA1 orelse Alg =:= ?DNS_ALG_NSEC3RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA256 orelse Alg =:= ?DNS_ALG_RSASHA512 -> MBin = strip_leading_zeros(binary:encode_unsigned(M)), EBin = strip_leading_zeros(binary:encode_unsigned(E)), ESize = byte_size(EBin), PKBin = case ESize of _ when ESize =< 16#FF -> <>; _ when ESize =< 16#FFFF -> <<0, ESize:16, EBin:ESize/binary, MBin/binary>>; _ -> erlang:error(badarg) end, {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = PKM }, CompMap ) when Alg =:= ?DNS_ALG_DSA orelse Alg =:= ?DNS_ALG_NSEC3DSA -> [P, Q, G, Y] = [ case X of <> -> I; X when is_binary(X) -> binary:decode_unsigned(X); X when is_integer(X) -> X end || X <- PKM ], M = byte_size(strip_leading_zeros(binary:encode_unsigned(P))), T = (M - 64) div 8, PKBin = <>, {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = PK }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = [E, M] }, CompMap ) when Alg =:= ?DNS_ALG_RSASHA1 orelse Alg =:= ?DNS_ALG_NSEC3RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA256 orelse Alg =:= ?DNS_ALG_RSASHA512 -> MBin = strip_leading_zeros(binary:encode_unsigned(M)), EBin = strip_leading_zeros(binary:encode_unsigned(E)), ESize = byte_size(EBin), PKBin = case ESize of _ when ESize =< 16#FF -> <>; _ when ESize =< 16#FFFF -> <<0, ESize:16, EBin:ESize/binary, MBin/binary>>; _ -> erlang:error(badarg) end, {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = PKM }, CompMap ) when Alg =:= ?DNS_ALG_DSA orelse Alg =:= ?DNS_ALG_NSEC3DSA -> [P, Q, G, Y] = [ case X of <> -> I; X when is_binary(X) -> binary:decode_unsigned(X); X when is_integer(X) -> X end || X <- PKM ], M = byte_size(strip_leading_zeros(binary:encode_unsigned(P))), T = (M - 64) div 8, PKBin = <>, {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = Alg, public_key = PK }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_ds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_cds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }, CompMap ) -> {<>, CompMap}; encode_rrdata(_Pos, _Class, #dns_rrdata_hinfo{cpu = CPU, os = OS}, CompMap) -> {encode_text([CPU, OS]), CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = <<>>, public_key = PublicKey }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D}, public_key = PublicKey }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D, E, F, G, H}, public_key = PublicKey }, CompMap ) -> { <>, CompMap }; encode_rrdata( _Pos, _Class, #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = DName, public_key = PublicKey }, CompMap ) -> DNameBin = encode_dname(DName), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_key{ type = Type, xt = XT, name_type = NameType, sig = Sig, protocol = Protocol, alg = Alg, public_key = PublicKey }, CompMap ) -> { <>, CompMap }; encode_rrdata( Pos, _Class, #dns_rrdata_kx{preference = Pref, exchange = Name}, CompMap ) -> encode_dname(<>, CompMap, Pos + 2, Name); encode_rrdata( _Pos, _Class, #dns_rrdata_loc{ size = Size, horiz = Horiz, vert = Vert, lat = Lat, lon = Lon, alt = Alt }, CompMap ) -> SizeEnc = encode_loc_size(Size), HorizEnc = encode_loc_size(Horiz), VertEnc = encode_loc_size(Vert), LatEnc = Lat + ?MAX_INT32, LonEnc = Lon + ?MAX_INT32, { <<0:8, SizeEnc:1/binary, HorizEnc:1/binary, VertEnc:1/binary, LatEnc:32, LonEnc:32, (Alt + 10000000):32>>, CompMap }; encode_rrdata(Pos, _Class, #dns_rrdata_mb{madname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata(Pos, _Class, #dns_rrdata_mg{madname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata( Pos, _Class, #dns_rrdata_minfo{rmailbx = RMB, emailbx = EMB}, CompMap ) -> {RMBBin, CompMap0} = encode_dname(CompMap, Pos, RMB), NewPos = Pos + byte_size(RMBBin), {EMBBin, NewCompMap} = encode_dname(CompMap0, NewPos, EMB), {<>, NewCompMap}; encode_rrdata(Pos, _Class, #dns_rrdata_mr{newname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata( Pos, _Class, #dns_rrdata_mx{preference = Pref, exchange = Name}, CompMap ) -> encode_dname(<>, CompMap, Pos + 2, Name); encode_rrdata( _Pos, _Class, #dns_rrdata_naptr{ order = Order, preference = Pref, flags = Flags, services = Svcs, regexp = Regexp, replacement = Replacement }, CompMap ) -> Bin0 = encode_string(<>, Flags), Bin1 = encode_string(Bin0, Svcs), Regexp0 = unicode:characters_to_binary(Regexp, unicode, utf8), Bin2 = encode_string(Bin1, Regexp0), ReplacementBin = encode_dname(Replacement), {<>, CompMap}; encode_rrdata(Pos, _Class, #dns_rrdata_ns{dname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata( _Pos, _Class, #dns_rrdata_nsec{ next_dname = NextDName, types = Types }, CompMap ) -> NextDNameBin = encode_dname(NextDName), TypesBin = encode_nsec_types(Types), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_nsec3{ hash_alg = HashAlg, opt_out = OptOut, iterations = Iterations, salt = Salt, hash = Hash, types = Types }, CompMap ) -> TypeBMP = encode_nsec_types(Types), OptOutN = encode_bool(OptOut), SaltLength = byte_size(Salt), HashLength = byte_size(Hash), { <>, CompMap }; encode_rrdata( _Pos, _Class, #dns_rrdata_nsec3param{ hash_alg = HashAlg, flags = Flags, iterations = Iterations, salt = Salt }, CompMap ) -> SaltLength = byte_size(Salt), {<>, CompMap}; encode_rrdata( Pos, _Class, #dns_rrdata_nxt{dname = NxtDName, types = Types}, CompMap ) -> {NextDNameBin, NewCompMap} = encode_dname(CompMap, Pos, NxtDName), BMP = encode_nxt_bmp(Types), {<>, NewCompMap}; encode_rrdata(Pos, _Class, #dns_rrdata_ptr{dname = Name}, CompMap) -> encode_dname(CompMap, Pos, Name); encode_rrdata(_Pos, _Class, #dns_rrdata_rp{mbox = Mbox, txt = Txt}, CompMap) -> MboxBin = encode_dname(Mbox), TxtBin = encode_dname(Txt), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_rrsig{ type_covered = TypeCovered, alg = Alg, labels = Labels, original_ttl = OriginalTTL, expiration = SigExpire, inception = SigIncept, key_tag = KeyTag, signers_name = SignersName, signature = Sig }, CompMap ) -> SignersNameBin = encode_dname(SignersName), { <>, CompMap }; encode_rrdata( Pos, _Class, #dns_rrdata_rt{preference = Pref, host = Name}, CompMap ) -> encode_dname(<>, CompMap, Pos + 2, Name); encode_rrdata( Pos, _Class, #dns_rrdata_soa{ mname = MName, rname = RName, serial = Serial, refresh = Refresh, retry = Retry, expire = Expire, minimum = Minimum }, CompMap ) -> {MNBin, MNCMap} = encode_dname(CompMap, Pos, MName), NewPos = Pos + byte_size(MNBin), {RNBin, RNCMap} = encode_dname(MNBin, MNCMap, NewPos, RName), {<>, RNCMap}; encode_rrdata(_Pos, _Class, #dns_rrdata_spf{spf = Strings}, CompMap) -> {encode_text(Strings), CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_srv{ priority = Pri, weight = Wght, port = Port, target = Target }, CompMap ) -> TargetBin = encode_dname(Target), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_sshfp{ alg = Alg, fp_type = FPType, fp = FingerPrint }, CompMap ) -> {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_svcb{ svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams }, CompMap ) -> TargetNameBin = encode_dname(TargetName), SvcParamsBin = encode_svcb_svc_params(SvcParams), {<>, CompMap}; encode_rrdata( _Pos, _Class, #dns_rrdata_tsig{ alg = Alg, time = Time, fudge = Fudge, mac = MAC, msgid = MsgId, err = Err, other = Other }, CompMap ) -> AlgBin = encode_dname(Alg), MACSize = byte_size(MAC), OtherLen = byte_size(Other), { <>, CompMap }; encode_rrdata(_Pos, _Class, #dns_rrdata_txt{txt = Strings}, CompMap) -> {encode_text(Strings), CompMap}; encode_rrdata(_Pos, _Class, Bin, CompMap) when is_binary(Bin) -> {Bin, CompMap}. -spec decode_loc_point(non_neg_integer()) -> dns:uint32(). decode_loc_point(P) when is_integer(P), P > ?MAX_INT32 -> P - ?MAX_INT32; decode_loc_point(P) when is_integer(P), P =< ?MAX_INT32 -> -(?MAX_INT32 - P). -spec bin_to_key_tag(<<_:32, _:_*8>>) -> char(). bin_to_key_tag(Binary) when is_binary(Binary) -> bin_to_key_tag(Binary, 0). -spec bin_to_key_tag(binary(), non_neg_integer()) -> char(). bin_to_key_tag(<<>>, AC) -> (AC + ((AC bsr 16) band 16#FFFF)) band 16#FFFF; bin_to_key_tag(<>, AC) -> bin_to_key_tag(Rest, AC + X); bin_to_key_tag(<>, AC) -> bin_to_key_tag(<<>>, AC + (X bsl 8)). -spec encode_loc_size(integer()) -> <<_:8>>. encode_loc_size(Size) when is_integer(Size) -> encode_loc_size(Size, 0). -spec encode_loc_size(integer(), non_neg_integer()) -> <<_:8>>. encode_loc_size(Size, Exponent) -> case Size rem round_pow(10, Exponent + 1) of Size -> Base = Size div round_pow(10, Exponent), <>; _ -> encode_loc_size(Size, Exponent + 1) end. -spec decode_nsec_types(binary()) -> [non_neg_integer()]. decode_nsec_types(Bin) -> decode_nsec_types(Bin, []). -spec decode_nsec_types(binary(), [non_neg_integer()]) -> [non_neg_integer()]. decode_nsec_types(<<>>, Types) -> lists:reverse(Types); decode_nsec_types(<>, Types) -> BaseNo = WindowNum * 256, NewTypes = decode_nsec_types(BMP, BaseNo, Types), decode_nsec_types(Rest, NewTypes). -spec decode_nsec_types(bitstring(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. decode_nsec_types(<<>>, _Num, Types) -> Types; decode_nsec_types(<<0:1, Rest/bitstring>>, Num, Types) -> decode_nsec_types(Rest, Num + 1, Types); decode_nsec_types(<<1:1, Rest/bitstring>>, Num, Types) -> decode_nsec_types(Rest, Num + 1, [Num | Types]). -spec encode_nsec_types([integer()]) -> binary(). encode_nsec_types([]) -> <<>>; encode_nsec_types([_ | _] = UnsortedTypes) -> [FirstType | _] = Types = lists:usort(UnsortedTypes), FirstWindowNum = FirstType div 256, FirstLastType = FirstWindowNum * 256, encode_nsec_types(<<>>, <<>>, FirstWindowNum, FirstLastType, Types). -spec encode_nsec_types(binary(), bitstring(), integer(), number(), [integer()]) -> <<_:16, _:_*8>>. encode_nsec_types(Bin, BMP0, WindowNum, _LastType, []) -> BMP = pad_bmp(BMP0), BMPSize = byte_size(BMP), <>; encode_nsec_types(Bin, BMP0, OldWindowNum, _LastType, [Type | _] = Types) when Type div 256 =/= OldWindowNum -> BMP = pad_bmp(BMP0), BMPSize = byte_size(BMP), NewBin = <>, NewBMP = <<>>, NewWindowNum = Type div 256, NewLastType = NewWindowNum * 256, encode_nsec_types(NewBin, NewBMP, NewWindowNum, NewLastType, Types); encode_nsec_types(Bin, BMP, WindowNum, LastType, [Type | Types]) -> PadBy = case LastType rem 256 of 0 -> Type rem 256; _ -> Type - LastType - 1 end, NewBMP = <>, encode_nsec_types(Bin, NewBMP, WindowNum, Type, Types). -spec decode_nxt_bmp(bitstring()) -> [non_neg_integer()]. decode_nxt_bmp(BMP) -> decode_nxt_bmp(BMP, 0, []). -spec decode_nxt_bmp(bitstring(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. decode_nxt_bmp(<<>>, _Offset, Types) -> lists:reverse(Types); decode_nxt_bmp(<<1:1, Rest/bitstring>>, Offset, Types) -> decode_nxt_bmp(Rest, Offset + 1, [Offset | Types]); decode_nxt_bmp(<<0:1, Rest/bitstring>>, Offset, Types) -> decode_nxt_bmp(Rest, Offset + 1, Types). -spec encode_nxt_bmp([non_neg_integer()]) -> bitstring(). encode_nxt_bmp(UnsortedTypes) when is_list(UnsortedTypes) -> Types = lists:usort(UnsortedTypes), encode_nxt_bmp(Types, 0, <<>>). -spec encode_nxt_bmp([non_neg_integer()], non_neg_integer(), bitstring()) -> bitstring(). encode_nxt_bmp([], _LastType, BMP) -> pad_bmp(BMP); encode_nxt_bmp([Type | Types], 0, BMP) -> NewBMP = <>, encode_nxt_bmp(Types, Type, NewBMP); encode_nxt_bmp([Type | Types], LastType, BMP) -> PadBy = Type - LastType - 1, NewBMP = <>, encode_nxt_bmp(Types, Type, NewBMP). -spec pad_bmp(bitstring()) -> bitstring(). pad_bmp(BMP) when is_binary(BMP) -> BMP; pad_bmp(BMP) when is_bitstring(BMP) -> PadBy = 8 - bit_size(BMP) rem 8, <>. %%%=================================================================== %%% EDNS data functions -spec decode_optrrdata(binary()) -> [optrr_elem()]. decode_optrrdata(<<>>) -> []; decode_optrrdata(Bin) -> decode_optrrdata(Bin, []). -spec decode_optrrdata(binary(), [optrr_elem()]) -> [optrr_elem()]. decode_optrrdata(<<>>, Opts) -> lists:reverse(Opts); decode_optrrdata(<>, Opts) -> NewOpt = do_decode_optrrdata(EOptNum, EOptBin), decode_optrrdata(Rest, [NewOpt | Opts]). -spec do_decode_optrrdata(uint16(), binary()) -> optrr_elem(). do_decode_optrrdata(?DNS_EOPTCODE_LLQ, <<1:16, OC:16, EC:16, Id:64, LeaseLife:32>>) -> #dns_opt_llq{opcode = OC, errorcode = EC, id = Id, leaselife = LeaseLife}; do_decode_optrrdata(?DNS_EOPTCODE_NSID, <>) -> #dns_opt_nsid{data = Data}; do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary>>) -> #dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}; do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary>>) -> #dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = <<>> }; do_decode_optrrdata( ?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary, Password/binary>> ) -> #dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = Password }; do_decode_optrrdata(?DNS_EOPTCODE_UL, <>) -> #dns_opt_ul{lease = Time}; do_decode_optrrdata(?DNS_EOPTCODE_ECS, <>) -> #dns_opt_ecs{ family = FAMILY, source_prefix_length = SRCPL, scope_prefix_length = SCOPEPL, address = Payload }; do_decode_optrrdata(EOpt, <>) -> #dns_opt_unknown{id = EOpt, bin = Bin}. -spec encode_optrrdata([optrr_elem()]) -> bitstring() | {integer(), binary()}. encode_optrrdata(Opts) when is_list(Opts) -> encode_optrrdata(Opts, <<>>). -spec encode_optrrdata([optrr_elem()], bitstring()) -> bitstring(). encode_optrrdata([], Bin) -> Bin; encode_optrrdata([Opt | Opts], Bin) -> {Id, NewBin} = do_encode_optrrdata(Opt), Len = byte_size(NewBin), encode_optrrdata(Opts, <>). do_encode_optrrdata(#dns_opt_llq{ opcode = OC, errorcode = EC, id = Id, leaselife = Length }) -> Data = <<1:16, OC:16, EC:16, Id:64, Length:32>>, {?DNS_EOPTCODE_LLQ, Data}; do_encode_optrrdata(#dns_opt_ul{lease = Lease}) -> {?DNS_EOPTCODE_UL, <>}; do_encode_optrrdata(#dns_opt_nsid{data = Data}) when is_binary(Data) -> {?DNS_EOPTCODE_NSID, Data}; do_encode_optrrdata(#dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = Password }) when byte_size(PMAC) =:= 6 andalso byte_size(WMAC) =:= 6 andalso (byte_size(Password) =:= 6 orelse byte_size(Password) =:= 4) -> Bin = <<0:8, S:8, PMAC/binary, WMAC/binary, Password/binary>>, {?DNS_EOPTCODE_OWNER, Bin}; do_encode_optrrdata(#dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = <<>> }) when byte_size(PMAC) =:= 6 andalso byte_size(WMAC) =:= 6 -> {?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC/binary, WMAC/binary>>}; do_encode_optrrdata(#dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}) when byte_size(PMAC) =:= 6 -> {?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC/binary>>}; do_encode_optrrdata( #dns_opt_ecs{ family = FAMILY, source_prefix_length = SRCPL, scope_prefix_length = SCOPEPL, address = ADDRESS } ) -> Data = <>, {?DNS_EOPTCODE_ECS, Data}; do_encode_optrrdata(#dns_opt_unknown{id = Id, bin = Data}) when is_integer(Id) andalso is_binary(Data) -> {Id, Data}. %%%=================================================================== %%% Domain name functions %%%=================================================================== %% @doc Compare two domain names insensitive of case. -spec compare_dname(dname(), dname()) -> boolean(). compare_dname(Name, Name) -> true; compare_dname(NameA, NameB) -> NameALwr = dname_to_lower(iolist_to_binary(NameA)), NameBLwr = dname_to_lower(iolist_to_binary(NameB)), NameALwr =:= NameBLwr. -spec decode_dname(nonempty_binary(), binary()) -> {binary(), binary()}. decode_dname(DataBin, MsgBin) -> RemBin = DataBin, decode_dname(DataBin, MsgBin, RemBin, <<>>, 0). -spec decode_dname(nonempty_binary(), binary(), _, binary(), non_neg_integer()) -> {binary(), binary()}. decode_dname(_DataBin, MsgBin, _RemBin, _DName, Count) when Count > byte_size(MsgBin) -> throw(decode_loop); decode_dname(<<0, DataRBin/binary>>, _MsgBin, RBin, DName0, Count) -> NewRemBin = choose_next_bin(Count, DataRBin, RBin), NewDName = case DName0 of <<$., DName/binary>> -> DName; <<>> -> <<>> end, {NewDName, NewRemBin}; decode_dname(<<0:2, Len:6, Label0:Len/binary, DataRemBin/binary>>, MsgBin, RemBin, DName, Count) -> Label = escape_label(Label0), NewRemBin = choose_next_bin(Count, DataRemBin, RemBin), NewDName = <>, decode_dname(DataRemBin, MsgBin, NewRemBin, NewDName, Count); decode_dname(<<3:2, Ptr:14, DataRBin/binary>>, MsgBin, RBin, DName, Count) -> NewRemBin = choose_next_bin(Count, DataRBin, RBin), NewRemBin = case Count of 0 -> DataRBin; _ -> RBin end, NewCount = Count + 2, case MsgBin of <<_:Ptr/binary, NewDataBin/binary>> -> decode_dname(NewDataBin, MsgBin, NewRemBin, DName, NewCount); _ -> throw(bad_pointer) end. choose_next_bin(0, DataRBin, _RBin) -> DataRBin; choose_next_bin(_, _DataRBin, RBin) -> RBin. %% @doc Escapes dots in a DNS label -spec escape_label(label()) -> label(). escape_label(Label) when is_binary(Label) -> escape_label(<<>>, Label). -spec escape_label(bitstring(), binary()) -> bitstring(). escape_label(Label, <<>>) -> Label; escape_label(Cur, <<$., Rest/binary>>) -> escape_label(<>, Rest); escape_label(Cur, <>) -> escape_label(<>, Rest). -spec decode_dnameonly(nonempty_binary(), binary()) -> binary(). decode_dnameonly(Bin, MsgBin) -> case decode_dname(Bin, MsgBin) of {DName, <<>>} -> DName; _ -> throw(trailing_garbage) end. -spec new_compmap() -> compmap(). new_compmap() -> #{}. %% @private -spec encode_dname(binary()) -> nonempty_binary(). encode_dname(Name) -> Labels = <<<<(byte_size(L)), L/binary>> || L <- dname_to_labels(Name)>>, <>. -spec encode_dname(compmap(), non_neg_integer(), binary()) -> {binary(), undefined | compmap()}. encode_dname(CompMap, Pos, Name) -> encode_dname(<<>>, CompMap, Pos, Name). -spec encode_dname(binary(), undefined | compmap(), non_neg_integer(), binary()) -> {binary(), undefined | compmap()}. encode_dname(Bin, undefined, _Pos, Name) -> DNameBin = encode_dname(Name), {<>, undefined}; encode_dname(Bin, CompMap, Pos, Name) -> Labels = dname_to_labels(Name), LwrLabels = dname_to_labels(dname_to_lower(Name)), encode_dname_labels(Bin, CompMap, Pos, Labels, LwrLabels). -spec encode_dname_labels(binary(), compmap(), non_neg_integer(), [binary()], [binary()]) -> {nonempty_binary(), compmap()}. encode_dname_labels(Bin, CompMap, _Pos, [], []) -> {<>, CompMap}; encode_dname_labels(Bin, CompMap, Pos, [L | Ls], [_ | LwrLs] = LwrLabels) -> case maps:get(LwrLabels, CompMap, undefined) of undefined -> NewCompMap = case Pos < (1 bsl 14) of true -> CompMap#{LwrLabels => Pos}; false -> CompMap end, Size = byte_size(L), NewPos = Pos + 1 + Size, encode_dname_labels( <>, NewCompMap, NewPos, Ls, LwrLs ); Ptr -> {<>, CompMap} end. %% @doc Splits a dname into a list of labels and removes unneeded escapes. -spec dname_to_labels(dname()) -> [label()]. dname_to_labels("") -> []; dname_to_labels(".") -> []; dname_to_labels(<<>>) -> []; dname_to_labels(<<$.>>) -> []; dname_to_labels(Name) -> dname_to_labels(<<>>, iolist_to_binary(Name)). -spec dname_to_labels(bitstring(), binary()) -> [bitstring(), ...]. dname_to_labels(Label, <<>>) -> [Label]; dname_to_labels(Label, <<$.>>) -> [Label]; dname_to_labels(Label, <<$., Cs/binary>>) -> [Label | dname_to_labels(<<>>, Cs)]; dname_to_labels(Label, <<"\\.", Cs/binary>>) -> dname_to_labels(<