%% ------------------------------------------------------------------- %% %% Copyright (c) 2010 Andrew Tunnell-Jones. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(dns). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(""" The `dns` module is the primary entry point for the functionality in this library. The module exports various types used in type specs, such as `t:message/0`, which indicates a `#dns_message{}` record, `t:query/0` which represents a single `#dns_query{}` record, `t:questions/0`, which represents a list of queries, etc. It also exports functions for encoding and decoding messages, TSIG supporting functions, and various utility functions for comparing domain names, converting domain names into different cases, converting to and from label lists, etc. """). -export([decode_message/1, encode_message/1, encode_message/2]). -export([verify_tsig/3, verify_tsig/4, add_tsig/5, add_tsig/6]). -export([compare_dname/2, compare_labels/2, escape_label/1]). -export([ dname_to_upper/1, dname_to_lower/1, dname_to_labels/1, labels_to_dname/1, dname_to_lower_labels/1 ]). -export([unix_time/0, unix_time/1]). -export([random_id/0]). -include_lib("dns_erlang/include/dns.hrl"). %% 2^31 - 1, the largest signed 32-bit integer value -define(MAX_INT32, ((1 bsl 31) - 1)). ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 2-bits integer"). -type uint2() :: 0..1. ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 4-bits integer"). -type uint4() :: 0..15. ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 8-bits integer"). -type uint8() :: 0..((1 bsl 8) - 1). ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 16-bits integer"). -type uint16() :: 0..((1 bsl 16) - 1). ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 32-bits integer"). -type uint32() :: 0..((1 bsl 32) - 1). ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 48-bits integer"). -type uint48() :: 0..((1 bsl 48) - 1). ?DOC(#{group => <<"Types: integer codes">>}). ?DOC("Unsigned 64-bits integer"). -type uint64() :: 0..((1 bsl 64) - 1). ?DOC(#{group => <<"Types: integer codes">>}). -type opcode() :: uint4(). ?DOC(#{group => <<"Types: integer codes">>}). -type rcode() :: uint4(). ?DOC(#{group => <<"Types: integer codes">>}). -type eoptcode() :: uint16(). ?DOC(#{group => <<"Types: integer codes">>}). -type ercode() :: 0 | 16. ?DOC(#{group => <<"Types: integer codes">>}). -type llqerrcode() :: 0..6. ?DOC(#{group => <<"Types: integer codes">>}). -type llqopcode() :: 1..3. -export_type([ uint2/0, uint4/0, uint8/0, uint16/0, uint32/0, uint48/0, uint64/0, opcode/0, rcode/0, ercode/0, eoptcode/0, llqopcode/0, llqerrcode/0 ]). ?DOC(""" DNS wire message format. The general form is a 96bits header, followed by a variable number of questions, answers, authorities, and additional records. """). -type message_bin() :: <<_:96, _:_*8>>. ?DOC("DNS Message ID. See RFC 1035: §4.1.1."). -type message_id() :: uint16(). ?DOC(""" Decoding errors. Can be one of the following: - `formerr`: the message was malformed. - `truncated`: the message was partially decoded, as data was found missing from the message. - `trailing_garbage`: the message was successfully decoded, but there was trailing garbage at the end of the message. """). -type decode_error() :: formerr | truncated | trailing_garbage. ?DOC("Domain name, expressed as a sequence of `t:label/0`, as defined in RFC 1035: §3.1."). -type dname() :: binary(). ?DOC(""" DNS labels. See RFC 1035: §2.3.1. The labels must follow the rules for ARPANET host names. They must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen. There are also some restrictions on the length. Labels must be 63 characters or less. """). -type label() :: binary(). ?DOC("A list of `t:dns:label/0`"). -type labels() :: [label()]. ?DOC("DNS Message class. See RFC 1035: §4.1.2."). -type class() :: uint16(). ?DOC("DNS Message class. See RFC 1035: §4.1.2."). -type type() :: uint16(). ?DOC("DNS Message class. See RFC 1035: §4.1.3."). -type ttl() :: 0..?MAX_INT32. ?DOC("Unix timestamp in seconds."). -type unix_time() :: 0..4294967295. -export_type([ message_bin/0, message_id/0, decode_error/0, dname/0, class/0, type/0, ttl/0, label/0, labels/0, unix_time/0 ]). ?DOC(#{group => <<"Types: records">>}). ?DOC("Main DNS message structure."). -type message() :: #dns_message{}. ?DOC(#{group => <<"Types: records">>}). -type query() :: #dns_query{}. ?DOC(#{group => <<"Types: records">>}). -type rr() :: #dns_rr{}. ?DOC(#{group => <<"Types: records">>}). -type optrr() :: #dns_optrr{}. ?DOC(#{group => <<"Types: records">>}). -type opt_nsid() :: #dns_opt_nsid{}. ?DOC(#{group => <<"Types: records">>}). -type opt_ul() :: #dns_opt_ul{}. ?DOC(#{group => <<"Types: records">>}). -type opt_ecs() :: #dns_opt_ecs{}. ?DOC(#{group => <<"Types: records">>}). -type opt_llq() :: #dns_opt_llq{}. ?DOC(#{group => <<"Types: records">>}). -type opt_owner() :: #dns_opt_owner{}. ?DOC(#{group => <<"Types: records">>}). -type opt_cookie() :: #dns_opt_cookie{}. ?DOC(#{group => <<"Types: records">>}). -type opt_ede() :: #dns_opt_ede{}. ?DOC(#{group => <<"Types: records">>}). -type opt_unknown() :: #dns_opt_unknown{}. ?DOC(#{group => <<"Types: records">>}). -type rrdata_rrsig() :: #dns_rrdata_rrsig{}. ?DOC(#{group => <<"Types: records">>}). -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_tlsa{} | #dns_rrdata_tsig{} | #dns_rrdata_txt{}. ?DOC(#{group => <<"Types: records">>}). -type records() :: additional() | answers() | authority() | questions(). ?DOC(#{group => <<"Types: records">>}). -type optrr_elem() :: opt_nsid() | opt_ul() | opt_unknown() | opt_ecs() | opt_llq() | opt_owner() | opt_cookie() | opt_ede(). ?DOC(#{group => <<"Types: records">>}). -type questions() :: [query()]. ?DOC(#{group => <<"Types: records">>}). -type answers() :: [rr()]. ?DOC(#{group => <<"Types: records">>}). -type authority() :: [rr()]. ?DOC(#{group => <<"Types: records">>}). -type additional() :: [optrr() | rr()]. -export_type([ message/0, records/0, query/0, rr/0, optrr/0, questions/0, answers/0, authority/0, additional/0, rrdata/0, opt_nsid/0, opt_ul/0, opt_ecs/0, opt_llq/0, opt_owner/0, opt_unknown/0, optrr_elem/0, rrdata_rrsig/0 ]). ?DOC(#{group => <<"Types: TSIG">>}). -type tsig_mac() :: binary(). ?DOC(#{group => <<"Types: TSIG">>}). -type tsig_error() :: 0 | 16..18. ?DOC(#{group => <<"Types: TSIG">>}). -type tsig_alg() :: binary(). ?DOC(#{group => <<"Types: TSIG">>}). -type alg() :: ?DNS_ALG_DSA | ?DNS_ALG_NSEC3DSA | ?DNS_ALG_RSASHA1 | ?DNS_ALG_NSEC3RSASHA1 | ?DNS_ALG_RSASHA256 | ?DNS_ALG_RSASHA512 | ?DNS_ALG_ECDSAP256SHA256 | ?DNS_ALG_ECDSAP384SHA384 | ?DNS_ALG_ED25519 | ?DNS_ALG_ED448. -export_type([ alg/0, tsig_mac/0, tsig_error/0, tsig_alg/0 ]). ?DOC(#{group => <<"Types: options">>}). -type svcb_svc_params() :: #{ 1..6 => none | char() | binary() }. ?DOC(#{group => <<"Types: options">>}). -type encode_message_opts() :: #{ max_size => 512..65535, tc_mode => default | axfr | llq_event, tsig => encode_tsig_opts() }. ?DOC(#{group => <<"Types: options">>}). -type encode_tsig_opts() :: #{ name := dname(), alg := tsig_alg(), msgid => message_id(), secret => binary(), errcode => tsig_error(), other => binary(), time => unix_time(), fudge => non_neg_integer(), mac => tsig_mac(), tail => boolean() }. ?DOC(#{group => <<"Types: options">>}). -type tsig_opts() :: #{ time => unix_time(), fudge => non_neg_integer(), mac => tsig_mac(), tail => boolean(), atom() => _ }. -export_type([ svcb_svc_params/0, encode_message_opts/0, encode_tsig_opts/0, tsig_opts/0 ]). %%%=================================================================== %%% Message body functions %%%=================================================================== ?DOC(#{group => <<"Functions: parsing">>}). ?DOC("Decode a binary DNS message."). -spec decode_message(message_bin()) -> {decode_error(), message() | undefined, binary()} | message(). decode_message(MsgBin) -> dns_decode:decode(MsgBin). ?DOC(#{group => <<"Functions: parsing">>}). ?DOC("Encode a `t:message/0` record."). -spec encode_message(message()) -> message_bin(). encode_message(Msg) -> dns_encode:encode(Msg). ?DOC(#{group => <<"Functions: parsing">>}). ?DOC("Encode a dns_message record - will truncate the message as needed."). -spec encode_message(message(), encode_message_opts()) -> {false, message_bin()} | {false, message_bin(), tsig_mac()} | {true, message_bin(), message()} | {true, message_bin(), tsig_mac(), message()}. encode_message(Msg, Opts) -> dns_encode:encode(Msg, Opts). %%%=================================================================== %%% TSIG functions %%%=================================================================== ?DOC(#{group => <<"Functions: TSIG">>}). ?DOC(#{equiv => verify_tsig(MsgBin, Name, Secret, #{})}). -spec verify_tsig(message_bin(), dname(), binary()) -> {ok, tsig_mac()} | {error, tsig_error()}. verify_tsig(MsgBin, Name, Secret) -> dns_tsig:verify_tsig(MsgBin, Name, Secret). ?DOC(#{group => <<"Functions: TSIG">>}). ?DOC("Verifies a TSIG message signature."). -spec verify_tsig(message_bin(), dname(), binary(), tsig_opts()) -> {ok, tsig_mac()} | {error, tsig_error()}. verify_tsig(MsgBin, Name, Secret, Options) -> dns_tsig:verify_tsig(MsgBin, Name, Secret, Options). ?DOC(#{group => <<"Functions: TSIG">>}). ?DOC(#{equiv => add_tsig(Msg, Alg, Name, Secret, ErrCode, #{name => Name, alg => Alg})}). -spec add_tsig(message(), tsig_alg(), dname(), binary(), tsig_error()) -> message(). add_tsig(Msg, Alg, Name, Secret, ErrCode) -> dns_tsig:add_tsig(Msg, Alg, Name, Secret, ErrCode, #{name => Name, alg => Alg}). ?DOC(#{group => <<"Functions: TSIG">>}). ?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(), encode_tsig_opts()) -> message(). add_tsig(Msg, Alg, Name, Secret, ErrCode, Options) -> dns_tsig:add_tsig(Msg, Alg, Name, Secret, ErrCode, Options). %%%=================================================================== %%% Domain name functions %%%=================================================================== ?DOC(#{group => <<"Functions: utilities">>}). ?DOC("Splits a dname into a list of labels in lowercase and removes unneeded escapes."). -spec dname_to_lower_labels(dname()) -> labels(). dname_to_lower_labels(Name) -> dname_to_labels(dname_to_lower(Name)). ?DOC(#{group => <<"Functions: utilities">>}). ?DOC("Splits a dname into a list of labels and removes unneeded escapes."). -spec dname_to_labels(dname()) -> labels(). dname_to_labels(<<>>) -> []; dname_to_labels(<<$.>>) -> []; dname_to_labels(Name) -> do_dname_to_labels(<<>>, Name). -spec do_dname_to_labels(binary(), binary()) -> [binary(), ...]. do_dname_to_labels(Label, <<>>) -> [Label]; do_dname_to_labels(Label, <<$.>>) -> [Label]; do_dname_to_labels(Label, <<$., Cs/binary>>) -> [Label | do_dname_to_labels(<<>>, Cs)]; do_dname_to_labels(Label, <<"\\.", Cs/binary>>) -> do_dname_to_labels(<