%% ------------------------------------------------------------------- %% %% 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). %% API -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]). -endif. -include("dns.hrl"). %% 2^31 - 1, the largest signed 32-bit integer value -define(MAX_INT32, ((1 bsl 31) - 1)). %% Types -type uint2() :: 0..1. -type uint4() :: 0..3. -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, 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, rrdata/0, tsig_alg/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 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()]] | [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. -define(DEFAULT_TSIG_FUDGE, 5 * 60). %%%=================================================================== %%% 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(), <<_:64, _:_*8>>) -> {[{_, _, _, _}], binary()} | {atom(), [{_, _, _, _}], binary()}. decode_message_questions(DataBin, Count, MsgBin) -> decode_message_questions(DataBin, Count, MsgBin, []). -spec decode_message_questions(binary(), char(), <<_:64, _:_*8>>, [ #dns_query{name :: binary(), class :: char(), type :: char()} ]) -> {[{_, _, _, _}], binary()} | {atom(), [{_, _, _, _}], 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) -> case catch 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}; 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) -> case catch 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}; 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 = Ad} = Msg, Opts) -> TCMode = proplists:get_value(tc_mode, Opts, default), ValidTCMode = lists:member(TCMode, [default, axfr, llq_event]), MaxSizeDefault = case Ad of [#dns_optrr{udp_payload_size = UPS} | _] -> UPS; _ -> 512 end, MaxSize = proplists:get_value(max_size, Opts, MaxSizeDefault), if not is_integer(MaxSize) -> erlang:error(badarg); MaxSize < 512 orelse 65535 < MaxSize -> erlang:error(badarg); not ValidTCMode -> erlang:error(badarg); true -> ok end, EncodeFun = case TCMode of default -> fun encode_message_default/2; axfr -> fun encode_message_axfr/2; llq_event -> fun encode_message_llq/2 end, 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 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{tc = TC, additional = Ad} = Msg, MaxSize) -> BuildHead = fun(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) end, {OptRRBin, Ad0} = encode_message_pop_optrr(Ad), Pos = 12, SpaceLeft = MaxSize - Pos, case encode_message_d_req(Pos, SpaceLeft, Msg) of {false, QC, ANC, AUC, Body} -> Head = BuildHead(true, QC, ANC, AUC, 0), <>; {CompMap, QC, ANC, AUC, Body} -> BodySize = byte_size(Body), OptRRBinSize = byte_size(OptRRBin), Pos0 = BodySize + Pos, case SpaceLeft - BodySize of SpaceLeft0 when SpaceLeft0 < OptRRBinSize -> Head = BuildHead(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 = BuildHead(false, QC, ANC, AUC, OptC), <>; {ADC, AdBin} -> Head = BuildHead(false, QC, ANC, AUC, OptC + ADC), <> end end end. -spec encode_message_d_req(12, number(), message()) -> {_, char(), char(), char(), bitstring()}. encode_message_d_req(Pos, SpaceLeft, #dns_message{} = Msg) -> Msg0 = Msg#dns_message{qc = 0, anc = 0, auc = 0}, encode_message_d_req(Pos, SpaceLeft, new_compmap(), <<>>, Msg0). -spec encode_message_d_req(pos_integer(), number(), _, bitstring(), message()) -> {_, char(), char(), char(), bitstring()}. encode_message_d_req( Pos, SpaceLeft, CompMap, Bin, #dns_message{qc = QC, anc = ANC, auc = AUC} = Msg ) -> 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, Msg0 = encode_message_put(Recs0, Section, Msg), Msg1 = encode_message_updatecount(EncodedLen, Section, Msg0), Bin0 = <>, case Recs0Len of 0 -> NewBinSize = byte_size(NewBin), Pos0 = Pos + NewBinSize, SpaceLeft0 = SpaceLeft - NewBinSize, encode_message_d_req( Pos0, SpaceLeft0, CompMap0, Bin0, Msg1 ); _ -> #dns_message{qc = QC0, anc = ANC0, auc = AUC0} = Msg1, {false, QC0, ANC0, AUC0, Bin0} end end. -spec encode_message_d_opt(pos_integer(), number(), _, [ [{_, _, _, _, _, _}] | optrr() | rr() ]) -> 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 = 12, SpaceLeft = MaxSize - Pos, encode_message_axfr(Pos, SpaceLeft, new_compmap(), <<>>, Msg). -spec encode_message_axfr(pos_integer(), number(), _, binary(), message()) -> binary() | {binary(), message()}. encode_message_axfr(Pos, SpaceLeft, CompMap, Bin, #dns_message{} = Msg) -> {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, Msg0 = encode_message_put(Recs0, Section, Msg), Msg1 = encode_message_updatecount(EncodedLen, Section, Msg0), 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(Pos0, SpaceLeft0, CompMap0, Bin0, Msg1); _ -> 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( [[rr()] | query() | optrr() | rr()], additional | answers | authority | questions, message() ) -> message(). encode_message_put(Recs, questions, #dns_message{} = Msg) -> Msg#dns_message{questions = Recs}; encode_message_put(Recs, answers, #dns_message{} = Msg) -> Msg#dns_message{answers = Recs}; encode_message_put(Recs, authority, #dns_message{} = Msg) -> Msg#dns_message{authority = Recs}; encode_message_put(Recs, additional, #dns_message{} = Msg) -> Msg#dns_message{additional = Recs}. -spec encode_message_a_setcounts(message()) -> message(). encode_message_a_setcounts( #dns_message{ questions = Q, answers = An, authority = Au, additional = Ad } = Msg ) -> Msg#dns_message{ qc = length(Q), anc = length(An), auc = length(Au), adc = length(Ad) }. -spec encode_message_updatecount( char(), additional | answers | authority | questions, message() ) -> message(). encode_message_updatecount(Count, questions, #dns_message{} = Msg) -> Msg#dns_message{qc = Count}; encode_message_updatecount(Count, answers, #dns_message{} = Msg) -> Msg#dns_message{anc = Count}; encode_message_updatecount(Count, authority, #dns_message{} = Msg) -> Msg#dns_message{auc = Count}; encode_message_updatecount(Count, additional, #dns_message{} = Msg) -> Msg#dns_message{adc = Count}. -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 = An, authority = Au, additional = Ad } = Msg, MaxSize ) -> QC = length(Q), AnC = length(An), AuC = length(Au), AdC = length(Ad), AuAd = Au ++ Ad, Pos = 12, 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, An), LeftoverAnC = length(LeftoverAn), EncodedAnC = AnC - 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 = AuC, adc = AdC}, 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(), _, [ [{_, _, _, _, _, _}] | query() | optrr() | rr() ]) -> {_, bitstring(), [[any()] | {_, _, _, _} | {_, _, _, _, _, _}]}. 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(), [ [{_, _, _, _, _, _}] | query() | optrr() | rr() ]) -> {_, bitstring(), [[any()] | {_, _, _, _} | {_, _, _, _, _, _}]}. 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, [] = Recs) -> {CompMap, Body, Recs}. -spec encode_message_rec(_, non_neg_integer(), query() | optrr() | rr()) -> {_, <<_: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{ udp_payload_size = UPS, ext_rcode = ExtRcode, version = Version, dnssec = DNSSEC, data = Data }) -> IntClass = UPS, DNSSECBit = encode_bool(DNSSEC), RRBin = encode_optrrdata(Data), RRBinSize = byte_size(RRBin), NewBin = <<0, 41:16, IntClass:16, ExtRcode:8, Version:8, DNSSECBit:1, 0:15, RRBinSize:16, RRBin/binary>>, {CompMap, NewBin}; 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([ [{_, _, _, _, _, _}] | optrr() | rr() ]) -> {binary(), [ [{_, _, _, _, _, _}] | optrr() | rr() ]}. encode_message_pop_optrr([ #dns_optrr{ udp_payload_size = UPS, ext_rcode = ExtRcode, version = Version, dnssec = DNSSEC, data = Data } | Rest ]) -> Class = UPS, DNSSECBit = encode_bool(DNSSEC), RRBin = encode_optrrdata(Data), RRBinSize = byte_size(RRBin), Bin = <<0, 41:16, Class:16, ExtRcode:8, Version:8, DNSSECBit:1, 0:15, RRBinSize:16, RRBin/binary>>, {Bin, Rest}; encode_message_pop_optrr(Other) -> {<<>>, Other}. %% @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) -> 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), {UnsignedMsgBin, #dns_rr{name = TName, data = TData}} = strip_tsig(MsgBin), case compare_dname(Name, TName) of true -> #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 -> if Now < (Time - Fudge) -> {error, ?DNS_TSIGERR_BADTIME}; Now > (Time + Fudge) -> {error, ?DNS_TSIGERR_BADTIME}; true -> {ok, SMAC} end; false -> {error, ?DNS_TSIGERR_BADSIG} end; {error, Error} -> {error, Error} end; false -> {error, ?DNS_TSIGERR_BADKEY} 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}} = TSIG_RR], <<>>} -> MsgBodyLen = byte_size(HRB) - byte_size(TSIGBin), {UnsignedBodyBin, TSIGBin} = split_binary(HRB, MsgBodyLen), UnsignedMsgBin = <>, {UnsignedMsgBin, TSIG_RR}; {[#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 = if MAC =:= <<>> -> MAC; true -> <<(byte_size(MAC)):16, MAC/binary>> end, Data = if Tail -> [PMAC, Msg, <>, <>]; true -> [ 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()) -> any(). encode_rrdata(Class, Data) -> {Bin, undefined} = encode_rrdata(0, Class, Data, undefined), Bin. -spec encode_rrdata(non_neg_integer(), _, rrdata(), _) -> {_, _}. 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 = if ESize =< 16#FF -> <>; ESize =< 16#FFFF -> <<0, ESize:16, EBin:ESize/binary, MBin/binary>>; true -> 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 = if ESize =< 16#FF -> <>; ESize =< 16#FFFF -> <<0, ESize:16, EBin:ESize/binary, MBin/binary>>; true -> 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([any()]) -> bitstring(). encode_nxt_bmp(UnsortedTypes) when is_list(UnsortedTypes) -> Types = lists:usort(UnsortedTypes), encode_nxt_bmp(0, Types, <<>>). -spec encode_nxt_bmp(_, [integer()], bitstring()) -> bitstring(). encode_nxt_bmp(_LastType, [], BMP) -> pad_bmp(BMP); encode_nxt_bmp(LastType, [Type | Types], BMP) -> PadBy = case LastType of 0 -> Type; LastType -> Type - LastType - 1 end, NewBMP = <>, encode_nxt_bmp(Type, Types, 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()) -> [Elem] when Elem :: opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner(). decode_optrrdata(<<>>) -> []; decode_optrrdata(Bin) -> decode_optrrdata(Bin, []). -spec decode_optrrdata(binary(), [Elem]) -> [Elem] when Elem :: opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner(). decode_optrrdata(<<>>, Opts) -> lists:reverse(Opts); decode_optrrdata(<>, Opts) -> NewOpt = decode_optrrdata_1(EOptNum, EOptBin), decode_optrrdata(Rest, [NewOpt | Opts]). -spec decode_optrrdata_1(uint16(), binary() | _) -> opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner(). decode_optrrdata_1(?DNS_EOPTCODE_LLQ, <<1:16, OC:16, EC:16, Id:64, LeaseLife:32>>) -> #dns_opt_llq{opcode = OC, errorcode = EC, id = Id, leaselife = LeaseLife}; decode_optrrdata_1(?DNS_EOPTCODE_NSID, <>) -> #dns_opt_nsid{data = Data}; decode_optrrdata_1(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary>>) -> #dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}; decode_optrrdata_1(?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 = <<>> }; decode_optrrdata_1( ?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 }; decode_optrrdata_1(?DNS_EOPTCODE_UL, <>) -> #dns_opt_ul{lease = Time}; decode_optrrdata_1(?DNS_EOPTCODE_ECS, <>) -> #dns_opt_ecs{ family = FAMILY, source_prefix_length = SRCPL, scope_prefix_length = SCOPEPL, address = Payload }; decode_optrrdata_1(EOpt, <>) -> #dns_opt_unknown{id = EOpt, bin = Bin}. -spec encode_optrrdata( [any()] | opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner() ) -> bitstring() | {integer(), binary()}. encode_optrrdata(Opts) when is_list(Opts) -> encode_optrrdata(lists:reverse(Opts), <<>>); 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}; encode_optrrdata(#dns_opt_ul{lease = Lease}) -> {?DNS_EOPTCODE_UL, <>}; encode_optrrdata(#dns_opt_nsid{data = Data}) when is_binary(Data) -> {?DNS_EOPTCODE_NSID, Data}; 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}; 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>>}; encode_optrrdata(#dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}) when byte_size(PMAC) =:= 6 -> {?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC/binary>>}; encode_optrrdata( #dns_opt_ecs{ family = FAMILY, source_prefix_length = SRCPL, scope_prefix_length = SCOPEPL, address = ADDRESS } ) -> Data = <>, {?DNS_EOPTCODE_ECS, Data}; encode_optrrdata(#dns_opt_unknown{id = Id, bin = Data}) when is_integer(Id) andalso is_binary(Data) -> {Id, Data}. -spec encode_optrrdata( [ [any()] | opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner() ], bitstring() ) -> bitstring(). encode_optrrdata([], Bin) -> Bin; encode_optrrdata([Opt | Opts], Bin) -> {Id, NewBin} = encode_optrrdata(Opt), Len = byte_size(NewBin), encode_optrrdata(Opts, <>). %%%=================================================================== %%% 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 = case Count of 0 -> DataRBin; _ -> RBin end, 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 = case Count of 0 -> DataRemBin; _ -> RemBin end, NewDname = <>, decode_dname(DataRemBin, MsgBin, NewRemBin, NewDname, Count); decode_dname(<<3:2, Ptr:14, DataRBin/binary>>, MsgBin, RBin, Dname, Count) -> 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. %% @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() -> gb_trees:tree(_, _). new_compmap() -> gb_trees:empty(). %% @private -spec encode_dname(binary()) -> nonempty_binary(). encode_dname(Name) -> Labels = <<<<(byte_size(L)), L/binary>> || L <- dname_to_labels(Name)>>, <>. -spec encode_dname(_, non_neg_integer(), binary()) -> {binary(), _}. encode_dname(CompMap, Pos, Name) -> encode_dname(<<>>, CompMap, Pos, Name). -spec encode_dname(binary(), _, non_neg_integer(), binary()) -> {binary(), _}. 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(), _, non_neg_integer(), [binary()], [binary()]) -> {nonempty_binary(), _}. encode_dname_labels(Bin, CompMap, _Pos, [], []) -> {<>, CompMap}; encode_dname_labels(Bin, CompMap, Pos, [L | Ls], [_ | LwrLs] = LwrLabels) -> case gb_trees:lookup(LwrLabels, CompMap) of {value, Ptr} -> {<>, CompMap}; none -> NewCompMap = case Pos < (1 bsl 14) of true -> gb_trees:insert(LwrLabels, Pos, CompMap); false -> CompMap end, Size = byte_size(L), NewPos = Pos + 1 + Size, encode_dname_labels( <>, NewCompMap, NewPos, Ls, LwrLs ) 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(<