%% ------------------------------------------------------------------- %% %% 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]). -include("dns.hrl"). %% Types -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]). -type decode_error() :: 'formerr' | 'truncated' | 'trailing_garbage'. -type message() :: #dns_message{}. -type message_bin() :: <<_:64,_:_*8>>. -type message_id() :: 1..65535. -type opcode() :: 0..16. -type rcode() :: 0..65535. -type 'query'() :: #dns_query{}. -type questions() :: ['query'()]. -type rr() :: #dns_rr{}. -type optrr() :: #dns_optrr{}. -type answers() :: [rr()]. -type authority() :: [rr()]. -type additional() :: [optrr()|[rr()]] | [rr()]. -type dname() :: binary(). -type label() :: binary(). -type class() :: 0..65535. -type type() :: 0..65535. -type ttl() :: 0..2147483647. -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_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. -include("dns_tests.hrl"). -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}. 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}. decode_message_questions(DataBin, Count, MsgBin) -> decode_message_questions(DataBin, Count, MsgBin, []). 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. decode_message_body(DataBin, Count, MsgBin) -> decode_message_body(DataBin, Count, MsgBin, []). 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. 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. 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. 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. 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 = encode_bool(case TC of true -> true; _ -> TCBool end)}, 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. 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). 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. encode_message_d_opt(Pos, SpaceLeft, CompMap, Recs) -> case encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs) of {_, Bin, []} -> {length(Recs), Bin}; _ -> false end. encode_message_axfr(#dns_message{} = Msg, MaxSize) -> Pos = 12, SpaceLeft = MaxSize - Pos, encode_message_axfr(Pos, SpaceLeft, new_compmap(), <<>>, Msg). 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. 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}. 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}. 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)}. 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}. 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}) -> <>. 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. encode_message_rec_list(Pos, SpaceLeft, CompMap, Recs) -> encode_message_rec_list(Pos, SpaceLeft, CompMap, <<>>, Recs). 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}. 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, <>}. 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}. 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. 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. hmac(TypeBin, Key, Data) -> case hmac_type(TypeBin) of undefined -> {error, bad_alg}; TypeAtom -> {ok, crypto:hmac(TypeAtom, Key, Data)} end. 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 decode_rrdata(Class, Type, Data) -> decode_rrdata(Class, Type, Data, <<>>). 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_txt(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_txt(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_TSIG, Bin, MsgBin) -> {Alg, <>} = decode_dname(Bin, MsgBin), #dns_rrdata_tsig{alg = Alg, time = Time, fudge = Fudge, mac = MAC, msgid = MsgID, other = Other, err = ErrInt}; decode_rrdata(_Class, ?DNS_TYPE_TXT, Bin, _MsgBin) -> #dns_rrdata_txt{txt = decode_txt(Bin)}; decode_rrdata(_Class, _Type, Bin, _MsgBin) -> Bin. %% @private encode_rrdata(Class, Data) -> {Bin, undefined} = encode_rrdata(0, Class, Data, undefined), Bin. 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 = 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_txt([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 + 2147483647, LonEnc = Lon + 2147483647, {<<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_txt(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_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_txt(Strings), CompMap}; encode_rrdata(_Pos, _Class, Bin, CompMap) when is_binary(Bin) -> {Bin, CompMap}. decode_loc_point(P) when is_integer(P) -> M = 2147483647, case P > M of true -> (P - M); false -> -1 * (M - P) end. bin_to_key_tag(Binary) when is_binary(Binary) -> bin_to_key_tag(Binary, 0). 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)). encode_loc_size(Size) when is_float(Size) -> encode_loc_size(round(Size)); encode_loc_size(Size) when is_integer(Size) -> encode_loc_size(Size, 0). 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. decode_nsec_types(Bin) when is_binary(Bin) -> decode_nsec_types(Bin, []). decode_nsec_types(<<>>, Types) -> lists:reverse(Types); decode_nsec_types(<>, Types) -> BaseNo = WindowNum * 256, NewTypes = decode_nsec_types(BaseNo, BMP, Types), decode_nsec_types(Rest, NewTypes). decode_nsec_types(_Num, <<>>, Types) -> Types; decode_nsec_types(Num, <<0:1, Rest/bitstring>>, Types) -> decode_nsec_types(Num + 1, Rest, Types); decode_nsec_types(Num, <<1:1, Rest/bitstring>>, Types) -> decode_nsec_types(Num + 1, Rest, [Num|Types]). 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). 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). decode_nxt_bmp(BMP) -> decode_nxt_bmp(0, BMP, []). decode_nxt_bmp(_Offset, <<>>, Types) -> lists:reverse(Types); decode_nxt_bmp(Offset, <<1:1, Rest/bitstring>>, Types) -> decode_nxt_bmp(Offset + 1, Rest, [Offset|Types]); decode_nxt_bmp(Offset, <<0:1, Rest/bitstring>>, Types) -> decode_nxt_bmp(Offset + 1, Rest, Types). encode_nxt_bmp(UnsortedTypes) when is_list(UnsortedTypes) -> Types = lists:usort(UnsortedTypes), encode_nxt_bmp(0, Types, <<>>). 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). 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 %%%=================================================================== decode_optrrdata(<<>>) -> []; decode_optrrdata(Bin) -> decode_optrrdata(Bin, []). decode_optrrdata(<>, Opts) -> NewOpt = decode_optrrdata(EOptNum, EOptBin), NewOpts = [NewOpt|Opts], case Rest of <<>> -> lists:reverse(NewOpts); Rest -> decode_optrrdata(Rest, NewOpts) end; 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}; decode_optrrdata(?DNS_EOPTCODE_NSID, Data) -> #dns_opt_nsid{data = Data}; decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary>>) -> #dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}; 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 = <<>>}; 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}; decode_optrrdata(?DNS_EOPTCODE_UL, <>) -> #dns_opt_ul{lease = Time}; decode_optrrdata(EOpt, Bin) -> #dns_opt_unknown{id = EOpt, bin = Bin}. 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_unknown{id = Id, bin = Data}) when is_integer(Id) andalso is_binary(Data) -> {Id, Data}. 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. decode_dname(DataBin, MsgBin) -> RemBin = DataBin, decode_dname(DataBin, MsgBin, RemBin, <<>>, 0). 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). escape_label(Label, <<>>) -> Label; escape_label(Cur, <<$., Rest/binary>>) -> escape_label(<>, Rest); escape_label(Cur, <>) -> escape_label(<>, Rest). decode_dnameonly(Bin, MsgBin) -> case decode_dname(Bin, MsgBin) of {Dname, <<>>} -> Dname; _ -> throw(trailing_garbage) end. new_compmap() -> gb_trees:empty(). %% @private encode_dname(Name) -> Labels = << <<(byte_size(L)), L/binary>> || L <- dname_to_labels(Name) >>, <>. encode_dname(CompMap, Pos, Name) -> encode_dname(<<>>, CompMap, Pos, Name). 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). 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)). 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(<