-module(dns_decode). -moduledoc false. -include_lib("dns_erlang/include/dns.hrl"). -define(CLASS_IS_IN(T), (T =:= ?DNS_CLASS_IN orelse T =:= ?DNS_CLASS_NONE)). -define(MAX_INT32, ((1 bsl 31) - 1)). -export([decode/1, decode_query/1]). -export([ decode_message_questions/3, decode_message_additional/3, decode_message_body/3, decode_rrdata/3 ]). -ifdef(TEST). -export([ decode_rrdata/4, decode_optrrdata/1, decode_svcb_svc_params/1 ]). -endif. -elvis([ {elvis_style, max_function_arity, #{ignore => [{dns_decode, create_message_from_header, 14}]}}, {elvis_style, dont_repeat_yourself, #{ignore => [{dns_decode, decode_query, 1}]}} ]). -compile({inline, [decode_bool/1, round_pow/1, create_message_from_header/14]}). -spec decode(dns:message_bin()) -> dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}. decode( <> = MsgBin ) -> Msg0 = create_message_from_header(Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC), decode_body(MsgBin, Rest0, Msg0); decode(<<_/binary>> = MsgBin) -> {formerr, undefined, MsgBin}. -spec decode_query(dns:message_bin()) -> dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}. decode_query( <> = MsgBin ) -> %% Header validation for DNS queries to prevent DoS attacks. %% - QR bit check: QR must be 0 for queries %% - TC bit check: queries should never be truncated %% - OC bit check: each opcode has its own sensible combination of section counts %% - RCODE in queries is typically 0 (NOERROR) but most servers don't enforce this validation case {QR, TC, OC, QC, ANC, AUC, ADC} of %% RFC 1035: Standard queries must have exactly 1 question, no answers, no authority. %% RFC 9619: A DNS message with OPCODE = 0 MUST NOT include a QDCOUNT parameter whose value %% is greater than 1. It follows that the Question section of a DNS message with OPCODE = 0 %% MUST NOT contain more than one question. {0, 0, ?DNS_OPCODE_QUERY, 1, 0, 0, _} -> Msg0 = create_message_from_header( Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC ), decode_body(MsgBin, Rest0, Msg0); %% RFC 7873: Cookie-only queries may have QDCOUNT=0 when an OPT record with a COOKIE option %% is present in the additional section. {0, 0, ?DNS_OPCODE_QUERY, 0, 0, 0, ADC} when ADC > 0 -> %% Allow QC=0 for cookie-only queries (RFC 7873) Msg0 = create_message_from_header( Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC ), decode_body(MsgBin, Rest0, Msg0); %% Expected: QR=0, TC=0, QC=1 (typically), ANC>=0 (may contain SOA), AUC>=0 %% rfc1996 §3.7, 3.11: NOTIFY may contain SOA record in Answer section. {0, 0, ?DNS_OPCODE_NOTIFY, 1, _, _, _} when 0 =:= AUC orelse 1 =:= AUC -> Msg0 = create_message_from_header( Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC ), decode_body(MsgBin, Rest0, Msg0); %% UPDATE (opcode 5) - rfc2136 %% Expected: QR=0, TC=0, QC=1 (ZONE section), ANC>=0 (PREREQ section), %% AUC>=0 (UPDATE section) %% rfc2136 §2.3: UPDATE has ZONE section (question), PREREQ section (answer), %% UPDATE section (authority). %% We allow any QC/ANC/AUC here as rfc2136 defines these sections for UPDATE. %% However, typical implementations expect QC=1 (ZONE section). {0, 0, ?DNS_OPCODE_UPDATE, _, _, _, _} -> Msg0 = create_message_from_header( Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC ), decode_body(MsgBin, Rest0, Msg0); %% IQUERY (opcode 1) - RFC 1035 (obsolete per rfc3425) %% STATUS (opcode 2) - RFC 1035 (Not commonly supported) %% DSO (opcode 6) - rfc8490 (we don't support it) {0, 0, _, _, _, _, _} when ?DNS_OPCODE_IQUERY =:= OC orelse ?DNS_OPCODE_STATUS =:= OC orelse ?DNS_OPCODE_DSO =:= OC -> create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0); %% Standard Query with invalid counts - reject with FORMERR %% This catches QUERY opcode with QC != 1, ANC > 0, or AUC > 0 {0, 0, ?DNS_OPCODE_QUERY, _, _, _, _} -> {formerr, undefined, MsgBin}; %% QR=1 (response) - reject with FORMERR %% RFC 1035: QR=0 indicates query, QR=1 indicates response. {1, _, _, _, _, _, _} -> {formerr, undefined, MsgBin}; %% TC=1 (truncated) - reject with FORMERR %% RFC 1035: TC bit indicates truncation due to message size limits. %% Truncation is a response mechanism; queries should not be truncated. {_, 1, _, _, _, _, _} -> {formerr, undefined, MsgBin}; %% Reserved/Unassigned opcodes (3, 7-15) - return NOTIMP %% IANA DNS Opcodes Registry: Opcodes 3 and 7-15 are reserved/unassigned. _ when OC =:= 3 orelse (OC >= 7 andalso OC =< 15) -> create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0) end; decode_query(MsgBin) -> {formerr, undefined, MsgBin}. %% Helper function to create a minimal message struct for NOTIMP response %% Returns {notimp, Message, Binary} where Message contains fields needed %% to construct NOTIMP response: %% - id: Preserved from query (required for response matching) %% - oc: Preserved from query (required to echo opcode in response) %% - rd: Preserved from query (required per DNS protocol) %% - cd: Preserved from query (if present) %% - rc: Set to NOTIMP (4) %% - qr: Set to true (response) %% - qc, questions: Parsed if possible, otherwise 0/[] %% - anc, auc, adc: Set to 0 (empty sections) -spec create_notimp_message( dns:message_bin(), dns:uint16(), dns:opcode(), 0 | 1, 0 | 1, dns:uint16(), binary() ) -> {notimp, dns:message(), binary()}. create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0) -> %% Try to parse question section if present (for Query/Notify opcodes) %% For IQUERY/STATUS, question format may differ, so we may not parse it {Questions, Rest} = case decode_message_questions(MsgBin, Rest0, QC) of {Qs, Rest1} -> {Qs, Rest1}; _ -> %% Parsing failed, return empty question list %% The response can still be sent with qc=0 {[], Rest0} end, Msg = #dns_message{ id = Id, %% Response qr = true, %% Preserve original opcode oc = OC, %% Preserve RD bit rd = decode_bool(RD), %% Preserve CD bit if present cd = decode_bool(CD), rc = ?DNS_RCODE_NOTIMP, qc = length(Questions), questions = Questions }, {notimp, Msg, Rest}. %% Helper function to create a dns_message record from parsed header fields -spec create_message_from_header( dns:uint16(), 0 | 1, dns:opcode(), 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, dns:rcode(), dns:uint16(), dns:uint16(), dns:uint16(), dns:uint16() ) -> dns:message(). create_message_from_header(Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC) -> #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 }. -spec decode_body(dns:message_bin(), binary(), dns:message()) -> dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}. decode_body(MsgBin, Rest0, #dns_message{} = Msg0) -> maybe {Msg1, Rest1} ?= decode_questions(MsgBin, Rest0, Msg0), {Msg2, Rest2} ?= decode_answers(MsgBin, Rest1, Msg1), {Msg3, Rest3} ?= decode_authority(MsgBin, Rest2, Msg2), {Msg4, Rest4} ?= decode_additional(MsgBin, Rest3, Msg3), decode_finished(MsgBin, Rest4, Msg4) else Other -> Other end. -spec decode_questions(dns:message_bin(), binary(), dns:message()) -> {dns:message(), binary()} | {dns:decode_error(), dns:message(), binary()}. decode_questions(MsgBin, Body, #dns_message{qc = QC} = Msg) -> case decode_message_questions(MsgBin, Body, QC, []) of {Questions, Rest} -> {Msg#dns_message{questions = Questions}, Rest}; {Error, Questions, Rest} -> {Error, Msg#dns_message{questions = Questions}, Rest} end. -spec decode_answers(dns:message_bin(), binary(), dns:message()) -> {dns:message(), binary()} | {dns:decode_error(), dns:message(), binary()}. decode_answers(MsgBin, Body, #dns_message{anc = ANC} = Msg) -> case decode_message_body(MsgBin, Body, ANC) of {RR, Rest} -> {Msg#dns_message{answers = RR}, Rest}; {Error, RR, Rest} -> {Error, Msg#dns_message{answers = RR}, Rest} end. -spec decode_authority(dns:message_bin(), binary(), dns:message()) -> {dns:message(), binary()} | {dns:decode_error(), dns:message(), binary()}. decode_authority(MsgBin, Body, #dns_message{auc = AUC} = Msg) -> case decode_message_body(MsgBin, Body, AUC) of {RR, Rest} -> {Msg#dns_message{authority = RR}, Rest}; {Error, RR, Rest} -> {Error, Msg#dns_message{authority = RR}, Rest} end. -spec decode_additional(dns:message_bin(), binary(), dns:message()) -> {dns:message(), binary()} | {dns:decode_error(), dns:message(), binary()}. decode_additional(MsgBin, Body, #dns_message{adc = ADC} = Msg) -> case decode_message_additional(MsgBin, Body, ADC) of {RR, Rest} -> {Msg#dns_message{additional = RR}, Rest}; {Error, RR, Rest} -> {Error, Msg#dns_message{additional = RR}, Rest} end. -spec decode_finished(dns:message_bin(), binary(), dns:message()) -> dns:message() | {dns:decode_error(), dns:message(), binary()}. decode_finished(_MsgBin, <<>>, #dns_message{} = Msg) -> Msg; decode_finished(_MsgBin, Bin, #dns_message{} = Msg) when is_binary(Bin) -> {trailing_garbage, Msg, Bin}. -spec decode_message_questions(dns:message_bin(), binary(), dns:uint16()) -> {dns:questions(), binary()} | {dns:decode_error(), dns:questions(), binary()}. decode_message_questions(MsgBin, DataBin, Count) -> decode_message_questions(MsgBin, DataBin, Count, []). -spec decode_message_questions(dns:message_bin(), binary(), dns:uint16(), dns:questions()) -> {dns:questions(), binary()} | {dns:decode_error(), dns:questions(), binary()}. decode_message_questions(_MsgBin, DataBin, 0, RRs) -> {lists:reverse(RRs), DataBin}; decode_message_questions(_MsgBin, <<>>, _Count, RRs) -> {truncated, lists:reverse(RRs), <<>>}; decode_message_questions(MsgBin, DataBin, Count, RRs) -> try dns_domain:from_wire(MsgBin, DataBin) of {Name, <>} -> R = #dns_query{name = Name, type = Type, class = Class}, decode_message_questions(MsgBin, RB, Count - 1, [R | RRs]); {_Name, _Bin} -> {truncated, lists:reverse(RRs), DataBin} catch Error when is_atom(Error) -> {Error, lists:reverse(RRs), DataBin}; _:_ -> {formerr, lists:reverse(RRs), DataBin} end. -spec decode_message_additional(dns:message_bin(), binary(), dns:uint16()) -> {dns:additional(), binary()} | {dns:decode_error(), [dns:optrr() | dns:rr()], binary()}. decode_message_additional(MsgBin, DataBin, Count) when is_binary(MsgBin), is_binary(DataBin), is_integer(Count), 0 =< Count, Count =< 65535 -> do_decode_message_additional(MsgBin, DataBin, Count, []). -spec do_decode_message_additional(dns:message_bin(), binary(), integer(), dns:additional()) -> {dns:additional(), binary()} | {dns:decode_error(), [dns:optrr() | dns:rr()], binary()}. do_decode_message_additional(_MsgBin, DataBin, 0, RRs) -> {lists:reverse(RRs), DataBin}; do_decode_message_additional(_MsgBin, <<>>, _Count, RRs) -> {truncated, lists:reverse(RRs), <<>>}; do_decode_message_additional(MsgBin, DataBin, Count, RRs) -> try dns_domain:from_wire(MsgBin, DataBin) of {<<>>, <>} -> Data = decode_optrrdata(EDataBin), RR = #dns_optrr{ udp_payload_size = UPS, ext_rcode = ExtRcode, version = Version, dnssec = decode_bool(DNSSEC), data = Data }, do_decode_message_additional(MsgBin, RemBin, Count - 1, [RR | RRs]); {Name, <>} -> RR = #dns_rr{ name = Name, type = Type, class = Class, ttl = TTL, data = decode_rrdata(MsgBin, Class, Type, RdataBin) }, do_decode_message_additional(MsgBin, RemBin, Count - 1, [RR | RRs]); {_Name, _Bin} -> {truncated, lists:reverse(RRs), DataBin} catch Error when is_atom(Error) -> {Error, lists:reverse(RRs), DataBin}; _:_ -> {formerr, lists:reverse(RRs), DataBin} end. -spec decode_message_body(dns:message_bin(), binary(), dns:uint16()) -> {[dns:rr()], binary()} | {dns:decode_error(), [dns:rr()], binary()}. decode_message_body(MsgBin, DataBin, Count) when is_binary(MsgBin), is_binary(MsgBin), is_integer(Count), 0 =< Count, Count =< 65535 -> do_decode_message_body(MsgBin, DataBin, Count, []). -spec do_decode_message_body(dns:message_bin(), binary(), integer(), [dns:rr()]) -> {[dns:rr()], binary()} | {dns:decode_error(), [dns:rr()], binary()}. do_decode_message_body(_MsgBin, DataBin, 0, RRs) -> {lists:reverse(RRs), DataBin}; do_decode_message_body(_MsgBin, <<>>, _Count, RRs) -> {truncated, lists:reverse(RRs), <<>>}; do_decode_message_body(MsgBin, DataBin, Count, RRs) -> try dns_domain:from_wire(MsgBin, DataBin) of {Name, <>} -> RR = #dns_rr{ name = Name, type = Type, class = Class, ttl = TTL, data = decode_rrdata(MsgBin, Class, Type, RdataBin) }, do_decode_message_body(MsgBin, RemBin, Count - 1, [RR | RRs]); {_Name, _Bin} -> {truncated, lists:reverse(RRs), DataBin} catch Error when is_atom(Error) -> {Error, lists:reverse(RRs), DataBin}; _:_ -> {formerr, lists:reverse(RRs), DataBin} end. -spec decode_optrrdata(binary()) -> [dns:optrr_elem()]. decode_optrrdata(Bin) -> decode_optrrdata(Bin, []). -spec decode_optrrdata(binary(), [dns:optrr_elem()]) -> [dns:optrr_elem()]. decode_optrrdata(<<>>, Opts) -> lists:reverse(Opts); decode_optrrdata(<>, Opts) -> NewOpt = do_decode_optrrdata(EOptNum, EOptBin), decode_optrrdata(Rest, [NewOpt | Opts]). -spec do_decode_optrrdata(dns:uint16(), binary()) -> dns:optrr_elem(). do_decode_optrrdata(?DNS_EOPTCODE_LLQ, <<1:16, OC:16, EC:16, Id:64, LeaseLife:32>>) -> #dns_opt_llq{opcode = OC, errorcode = EC, id = Id, leaselife = LeaseLife}; do_decode_optrrdata(?DNS_EOPTCODE_NSID, <>) -> #dns_opt_nsid{data = Data}; do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary>>) -> #dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>}; do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary>>) -> #dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = <<>> }; do_decode_optrrdata( ?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary, Password/binary>> ) -> #dns_opt_owner{ seq = S, primary_mac = PMAC, wakeup_mac = WMAC, password = Password }; do_decode_optrrdata(?DNS_EOPTCODE_UL, <>) -> #dns_opt_ul{lease = Time}; do_decode_optrrdata(?DNS_EOPTCODE_ECS, <>) -> #dns_opt_ecs{ family = FAMILY, source_prefix_length = SRCPL, scope_prefix_length = SCOPEPL, address = Payload }; do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, <>) -> #dns_opt_cookie{client = ClientCookie}; do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, <>) when 8 =< byte_size(ServerCookie), byte_size(ServerCookie) =< 32 -> #dns_opt_cookie{client = ClientCookie, server = ServerCookie}; do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, _) -> erlang:error(bad_cookie); do_decode_optrrdata(?DNS_EOPTCODE_EDE, <>) -> #dns_opt_ede{info_code = InfoCode, extra_text = ExtraText}; do_decode_optrrdata(?DNS_EOPTCODE_EDE, <<>>) -> #dns_opt_ede{info_code = 0, extra_text = <<>>}; do_decode_optrrdata(EOpt, <>) -> #dns_opt_unknown{id = EOpt, bin = Bin}. -spec decode_rrdata(dns:message_bin(), dns:uint16(), dns:uint16()) -> dns:rrdata(). decode_rrdata(MsgBin, Class, Type) -> decode_rrdata(MsgBin, Class, Type, MsgBin). -spec decode_rrdata(dns:message_bin(), dns:uint16(), dns:uint16(), binary()) -> dns:rrdata(). decode_rrdata(_MsgBin, _Class, _Type, <<>>) -> <<>>; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_A, <>) when ?CLASS_IS_IN(Class) -> #dns_rrdata_a{ip = {A, B, C, D}}; decode_rrdata( _MsgBin, Class, ?DNS_TYPE_AAAA, <> ) when ?CLASS_IS_IN(Class) -> #dns_rrdata_aaaa{ip = {A, B, C, D, E, F, G, H}}; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_EUI48, Bin) when ?CLASS_IS_IN(Class) andalso 6 =:= byte_size(Bin) -> #dns_rrdata_eui48{address = Bin}; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_EUI64, Bin) when ?CLASS_IS_IN(Class) andalso 8 =:= byte_size(Bin) -> #dns_rrdata_eui64{address = Bin}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_AFSDB, <>) -> #dns_rrdata_afsdb{ subtype = Subtype, hostname = decode_dnameonly(MsgBin, Bin) }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CAA, <>) -> <> = Bin, #dns_rrdata_caa{flags = Flags, tag = Tag, value = Value}; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CERT, <>) -> #dns_rrdata_cert{type = Type, keytag = KeyTag, alg = Alg, cert = Bin}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_CNAME, Bin) -> #dns_rrdata_cname{dname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_DHCID, Bin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_dhcid{data = Bin}; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_OPENPGPKEY, Bin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_openpgpkey{data = Bin}; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_URI, <> ) -> case uri_string:normalize(Target) of {error, Reason, _} -> erlang:error({bad_uri, Target, Reason}); NormalizedTarget -> #dns_rrdata_uri{ priority = Priority, weight = Weight, target = NormalizedTarget } end; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_RESINFO, Bin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_resinfo{data = decode_text(Bin)}; decode_rrdata(_MsgBin, Class, ?DNS_TYPE_WALLET, Bin) when ?CLASS_IS_IN(Class) -> #dns_rrdata_wallet{data = Bin}; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_DLV, <>) -> #dns_rrdata_dlv{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_DNAME, Bin) -> #dns_rrdata_dname{dname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_DNSKEY, <> = Bin ) when AlgNum =:= ?DNS_ALG_RSASHA1 orelse AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse AlgNum =:= ?DNS_ALG_RSASHA256 orelse AlgNum =:= ?DNS_ALG_RSASHA512 -> {Key, KeyTag} = decode_rsa_key(PublicKey, Bin), #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, keytag = KeyTag }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_DNSKEY, <> = Bin ) when (AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso T =< 8 -> {Key, KeyTag} = decode_dsa_key(T, Q, KeyBin, Bin), #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, keytag = KeyTag }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_DNSKEY, <> = Bin ) when (AlgNum =:= ?DNS_ALG_ECDSAP256SHA256 andalso 64 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ECDSAP384SHA384 andalso 96 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ED25519 andalso 32 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ED448 andalso 57 =:= byte_size(PublicKey)) -> #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, keytag = bin_to_key_tag(Bin) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_DNSKEY, <> = Bin ) -> #dns_rrdata_dnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, keytag = bin_to_key_tag(Bin) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_CDNSKEY, <> = Bin ) when AlgNum =:= ?DNS_ALG_RSASHA1 orelse AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse AlgNum =:= ?DNS_ALG_RSASHA256 orelse AlgNum =:= ?DNS_ALG_RSASHA512 -> {Key, KeyTag} = decode_rsa_key(PublicKey, Bin), #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, keytag = KeyTag }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_CDNSKEY, <> = Bin ) when (AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso T =< 8 -> {Key, KeyTag} = decode_dsa_key(T, Q, KeyBin, Bin), #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = Key, keytag = KeyTag }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_CDNSKEY, <> = Bin ) when (AlgNum =:= ?DNS_ALG_ECDSAP256SHA256 andalso 64 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ECDSAP384SHA384 andalso 96 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ED25519 andalso 32 =:= byte_size(PublicKey)) orelse (AlgNum =:= ?DNS_ALG_ED448 andalso 57 =:= byte_size(PublicKey)) -> #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, keytag = bin_to_key_tag(Bin) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_CDNSKEY, <> = Bin ) -> #dns_rrdata_cdnskey{ flags = Flags, protocol = Protocol, alg = AlgNum, public_key = PublicKey, keytag = bin_to_key_tag(Bin) }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_DS, <>) -> #dns_rrdata_ds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CDS, <>) -> #dns_rrdata_cds{ keytag = KeyTag, alg = Alg, digest_type = DigestType, digest = Digest }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_ZONEMD, <>) -> #dns_rrdata_zonemd{ serial = Serial, scheme = Scheme, algorithm = Alg, hash = Hash }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_HINFO, Bin) -> [CPU, OS] = decode_text(Bin), #dns_rrdata_hinfo{cpu = CPU, os = OS}; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_IPSECKEY, <> ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = <<>>, public_key = PublicKey }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_IPSECKEY, <> ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D}, public_key = PublicKey }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_IPSECKEY, <> ) -> #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = {A, B, C, D, E, F, G, H}, public_key = PublicKey }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_IPSECKEY, <>) -> {Gateway, PublicKey} = dns_domain:from_wire(MsgBin, Bin), #dns_rrdata_ipseckey{ precedence = Precedence, alg = Algorithm, gateway = Gateway, public_key = PublicKey }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_KEY, <> ) -> #dns_rrdata_key{ type = Type, xt = XT, name_type = NamType, sig = Sig, protocol = Protocol, alg = Alg, public_key = PublicKey }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_KX, <>) -> #dns_rrdata_kx{ preference = Preference, exchange = decode_dnameonly(MsgBin, Bin) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_LOC, <<0:8, SizeB:4, SizeE:4, HorizB:4, HorizE:4, VertB:4, VertE:4, LatPre:32, LonPre:32, AltPre:32>> ) when SizeE < 10 andalso HorizE < 10 andalso VertE < 10 -> #dns_rrdata_loc{ size = SizeB * (round_pow(SizeE)), horiz = HorizB * (round_pow(HorizE)), vert = VertB * (round_pow(VertE)), lat = decode_loc_point(LatPre), lon = decode_loc_point(LonPre), alt = AltPre - 10000000 }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MB, Bin) -> #dns_rrdata_mb{madname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MG, Bin) -> #dns_rrdata_mg{madname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MINFO, Bin) when is_binary(Bin) -> {RMB, EMB} = dns_domain:from_wire(Bin, MsgBin), #dns_rrdata_minfo{rmailbx = RMB, emailbx = decode_dnameonly(MsgBin, EMB)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MR, Bin) -> #dns_rrdata_mr{newname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MX, <>) -> #dns_rrdata_mx{ preference = Preference, exchange = decode_dnameonly(MsgBin, Bin) }; decode_rrdata( MsgBin, _Class, ?DNS_TYPE_NAPTR, <> ) -> {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(MsgBin, Bin3) }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NS, Bin) -> #dns_rrdata_ns{dname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NSEC, Bin) -> {NextDName, TypeBMP} = dns_domain:from_wire(MsgBin, Bin), Types = decode_nsec_types(TypeBMP), #dns_rrdata_nsec{next_dname = NextDName, types = Types}; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_CSYNC, <> ) -> Types = decode_nsec_types(TypeBMP), #dns_rrdata_csync{ soa_serial = SOASerial, flags = Flags, types = Types }; decode_rrdata( MsgBin, _Class, ?DNS_TYPE_DSYNC, <> ) -> Target = decode_dnameonly(MsgBin, TargetBin), #dns_rrdata_dsync{ rrtype = RRType, scheme = Scheme, port = Port, target = Target }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_NSEC3, <> ) -> #dns_rrdata_nsec3{ hash_alg = HashAlg, opt_out = decode_bool(OptOut), iterations = Iterations, salt = Salt, hash = Hash, types = decode_nsec_types(TypeBMP) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_NSEC3PARAM, <> ) -> #dns_rrdata_nsec3param{ hash_alg = Alg, flags = Flags, iterations = Iterations, salt = Salt }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_TLSA, <> ) -> #dns_rrdata_tlsa{ usage = Usage, selector = Selector, matching_type = MatchingType, certificate = Certificate }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_SMIMEA, <> ) -> #dns_rrdata_smimea{ usage = Usage, selector = Selector, matching_type = MatchingType, certificate = Certificate }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NXT, Bin) -> {NxtDName, BMP} = dns_domain:from_wire(MsgBin, Bin), #dns_rrdata_nxt{dname = NxtDName, types = decode_nxt_bmp(BMP)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_PTR, Bin) -> #dns_rrdata_ptr{dname = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_RP, Bin) -> {Mbox, TxtBin} = dns_domain:from_wire(MsgBin, Bin), #dns_rrdata_rp{mbox = Mbox, txt = decode_dnameonly(MsgBin, TxtBin)}; decode_rrdata( MsgBin, _Class, ?DNS_TYPE_RRSIG, <> ) -> {SigName, Sig} = dns_domain:from_wire(MsgBin, Bin), #dns_rrdata_rrsig{ type_covered = Type, alg = Alg, labels = Labels, original_ttl = TTL, expiration = Expire, inception = Inception, keytag = KeyTag, signers_name = SigName, signature = Sig }; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_RT, <>) -> #dns_rrdata_rt{preference = Pref, host = decode_dnameonly(MsgBin, Bin)}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_SOA, Bin) -> {MName, RNBin} = dns_domain:from_wire(MsgBin, Bin), {RName, Rest} = dns_domain:from_wire(MsgBin, RNBin), <> = Rest, #dns_rrdata_soa{ mname = MName, rname = RName, serial = Ser, refresh = Ref, retry = Ret, expire = Exp, minimum = Min }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_SPF, Bin) -> #dns_rrdata_spf{spf = decode_text(Bin)}; decode_rrdata( MsgBin, _Class, ?DNS_TYPE_SRV, <> ) -> #dns_rrdata_srv{ priority = Pri, weight = Wght, port = Port, target = decode_dnameonly(MsgBin, Bin) }; decode_rrdata( _MsgBin, _Class, ?DNS_TYPE_SSHFP, <> ) -> #dns_rrdata_sshfp{alg = Alg, fp_type = FPType, fp = FingerPrint}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_SVCB, <>) -> {TargetName, SvcParamsBin} = dns_domain:from_wire(MsgBin, Bin), SvcParams = decode_svcb_svc_params(SvcParamsBin), #dns_rrdata_svcb{svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_HTTPS, <>) -> {TargetName, SvcParamsBin} = dns_domain:from_wire(MsgBin, Bin), SvcParams = decode_svcb_svc_params(SvcParamsBin), #dns_rrdata_https{svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams}; decode_rrdata(MsgBin, _Class, ?DNS_TYPE_TSIG, Bin) -> {Alg, <>} = dns_domain:from_wire(MsgBin, Bin), #dns_rrdata_tsig{ alg = Alg, time = Time, fudge = Fudge, mac = MAC, msgid = MsgId, err = ErrInt, other = Other }; decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_TXT, Bin) -> #dns_rrdata_txt{txt = decode_text(Bin)}; decode_rrdata(_MsgBin, _Class, _Type, Bin) -> Bin. -spec decode_dnameonly(dns:message_bin(), nonempty_binary()) -> binary(). decode_dnameonly(MsgBin, Bin) -> case dns_domain:from_wire(MsgBin, Bin) of {DName, <<>>} -> DName; _ -> error(trailing_garbage) end. %% Helper function to decode RSA keys for DNSKEY and CDNSKEY records -spec decode_rsa_key(binary(), binary()) -> {list(), dns:uint16()}. decode_rsa_key(PublicKey, Bin) -> 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), {Key, KeyTag}. %% Helper function to decode DSA keys for DNSKEY and CDNSKEY records -spec decode_dsa_key(byte(), non_neg_integer(), binary(), binary()) -> {list(), dns:uint16()}. decode_dsa_key(T, Q, KeyBin, Bin) -> S = 64 + T * 8, <> = KeyBin, Key = [P, Q, G, Y], KeyTag = bin_to_key_tag(Bin), {Key, KeyTag}. -spec decode_text(binary()) -> [binary()]. decode_text(<<>>) -> []; decode_text(Bin) when is_binary(Bin) -> {RB, String} = decode_string(Bin), [String | decode_text(RB)]. -spec decode_string(nonempty_binary()) -> {binary(), binary()}. decode_string(<>) -> {Rest, Bin}. -spec bin_to_key_tag(<<_:32, _:_*8>>) -> dns:uint16(). bin_to_key_tag(Binary) when is_binary(Binary) -> do_bin_to_key_tag(Binary, 0). -spec do_bin_to_key_tag(binary(), non_neg_integer()) -> dns:uint16(). do_bin_to_key_tag(<<>>, AC) -> (AC + ((AC bsr 16) band 16#FFFF)) band 16#FFFF; do_bin_to_key_tag(<>, AC) -> do_bin_to_key_tag(Rest, AC + X); do_bin_to_key_tag(<>, AC) -> do_bin_to_key_tag(<<>>, AC + (X bsl 8)). -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 decode_nsec_types(binary()) -> [non_neg_integer()]. decode_nsec_types(Bin) -> do_decode_nsec_types(Bin, []). -spec do_decode_nsec_types(binary(), [non_neg_integer()]) -> [non_neg_integer()]. do_decode_nsec_types(<<>>, Types) -> lists:reverse(Types); do_decode_nsec_types(<>, Types) -> BaseNo = WindowNum * 256, NewTypes = do_decode_nsec_types(BMP, BaseNo, Types), do_decode_nsec_types(Rest, NewTypes). -spec do_decode_nsec_types(bitstring(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. do_decode_nsec_types(<<>>, _Num, Types) -> Types; do_decode_nsec_types(<<0:1, Rest/bitstring>>, Num, Types) -> do_decode_nsec_types(Rest, Num + 1, Types); do_decode_nsec_types(<<1:1, Rest/bitstring>>, Num, Types) -> do_decode_nsec_types(Rest, Num + 1, [Num | Types]). -spec decode_nxt_bmp(bitstring()) -> [non_neg_integer()]. decode_nxt_bmp(BMP) -> do_decode_nxt_bmp(BMP, 0, []). -spec do_decode_nxt_bmp(bitstring(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. do_decode_nxt_bmp(<<>>, _Offset, Types) -> lists:reverse(Types); do_decode_nxt_bmp(<<1:1, Rest/bitstring>>, Offset, Types) -> do_decode_nxt_bmp(Rest, Offset + 1, [Offset | Types]); do_decode_nxt_bmp(<<0:1, Rest/bitstring>>, Offset, Types) -> do_decode_nxt_bmp(Rest, Offset + 1, Types). -spec decode_svcb_svc_params(binary()) -> dns:svcb_svc_params(). decode_svcb_svc_params(Bin) -> dns_svcb_params:from_wire(Bin). -spec decode_bool(0 | 1) -> boolean(). decode_bool(0) -> false; decode_bool(1) -> true. -spec round_pow(non_neg_integer()) -> integer(). round_pow(E) -> element( E + 1, {1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000, 10_000_000_000} ).