Packages
erldns
10.0.0
11.0.2
11.0.1
11.0.0
10.6.0
10.5.6
10.5.5
10.5.4
10.5.3
10.5.2
10.5.1
10.5.0
10.4.4
10.4.3
10.4.2
10.4.1
10.4.0
10.3.0
10.2.1
10.2.0
10.1.0
10.0.0
10.0.0-rc4
10.0.0-rc3
10.0.0-rc2
10.0.0-rc1
9.1.0
9.0.0
9.0.0-rc3
9.0.0-rc2
9.0.0-rc1
8.1.0
8.0.0
8.0.0-rc6
8.0.0-rc5
8.0.0-rc4
8.0.0-rc3
8.0.0-rc2
8.0.0-rc1
7.0.0
7.0.0-rc9
7.0.0-rc8
7.0.0-rc7
7.0.0-rc6
7.0.0-rc5
7.0.0-rc4
7.0.0-rc3
7.0.0-rc2
7.0.0-rc12
7.0.0-rc11
7.0.0-rc10
7.0.0-rc1
6.0.2
6.0.1
6.0.0
5.0.0
4.3.1
4.3.0
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.0
3.0.0
1.0.0
Erlang Authoritative DNS Server
Current section
Files
Jump to
Current section
Files
src/zones/erldns_zone_encoder.erl
-module(erldns_zone_encoder).
-moduledoc false.
-include_lib("dns_erlang/include/dns.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("erldns/include/erldns.hrl").
-define(LOG_METADATA, #{domain => [erldns, zones, encoder]}).
-export([encode/3]).
-spec encode(erldns:zone(), #{atom() => dynamic()}, [erldns_zone_codec:encoder()]) ->
not_implemented | json:encode_value().
encode(Zone, #{mode := zone_records_to_json}, Encoders) ->
encode_zone_records_to_json(Zone, Encoders);
encode(Zone, #{mode := {zone_records_to_json, RecordName}}, Encoders) ->
encode_zone_records_to_json(Zone, RecordName, Encoders);
encode(Zone, #{mode := {zone_records_to_json, RecordName, RecordType}}, Encoders) ->
encode_zone_records_to_json(Zone, RecordName, RecordType, Encoders);
encode(Zone, #{mode := zone_meta_to_json}, _) ->
zone_meta_to_json(Zone, #{});
encode(Zone, #{mode := zone_to_json}, Encoders) ->
Records = records_to_json(Zone, Encoders),
Extra = #{~"records" => Records},
zone_meta_to_json(Zone, Extra).
% Note: Private key material is purposely omitted
-spec zone_meta_to_json(erldns:zone(), dynamic()) -> json:encode_value().
zone_meta_to_json(Zone, MaybeRecords) ->
Zone0 = #{
~"name" => Zone#zone.name,
~"version" => Zone#zone.version,
~"records_count" => Zone#zone.record_count
},
ZoneJson = maps:merge(Zone0, MaybeRecords),
#{~"erldns" => #{~"zone" => ZoneJson}}.
% Internal API
encode_zone_records_to_json(Zone, Encoders) ->
Records = erldns_zone_cache:get_zone_records(Zone),
lists:flatmap(encode(Encoders), Records).
encode_zone_records_to_json(Zone, RecordName, Encoders) ->
Records = erldns_zone_cache:get_records_by_name(Zone, RecordName),
lists:flatmap(encode(Encoders), Records).
encode_zone_records_to_json(Zone, RecordName, RecordType, Encoders) ->
Type = dns_names:name_type(RecordType),
Records = erldns_zone_cache:get_records_by_name_and_type(Zone, RecordName, Type),
lists:flatmap(encode(Encoders), Records).
records_to_json(Zone, Encoders) ->
lists:flatmap(encode(Encoders), erldns_zone_cache:get_zone_records(Zone)).
encode(Encoders) ->
fun(Record) -> encode_record(Record, Encoders) end.
encode_record(Record, Encoders) ->
case encode_record(Record) of
not_implemented ->
try_custom_encoders(Record, Encoders);
EncodedRecord ->
[EncodedRecord]
end.
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_SOA, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_NS, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_A, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_AAAA, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_CNAME, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_MX, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_HINFO, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_TXT, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_SSHFP, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_SRV, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_NAPTR, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_CAA, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_DS, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_CDS, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_DNSKEY, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_CDNSKEY, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_RRSIG, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_TLSA, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_SVCB, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_HTTPS, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_OPENPGPKEY, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_SMIMEA, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_URI, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_WALLET, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_EUI48, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_EUI64, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_CSYNC, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_DSYNC, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(#dns_rr{name = Name, type = Type = ?DNS_TYPE_ZONEMD, ttl = Ttl, data = Data}) ->
encode_record(Name, Type, Ttl, Data);
encode_record(Record) ->
?LOG_WARNING(#{what => unable_to_encode_record, record => Record}, ?LOG_METADATA),
not_implemented.
encode_record(Name, Type, Ttl, Data) ->
#{
~"name" => erlang:iolist_to_binary(io_lib:format("~s.", [Name])),
~"type" => dns_names:type_name(Type),
~"ttl" => Ttl,
~"content" => encode_data(Data)
}.
try_custom_encoders(_, []) ->
[];
try_custom_encoders(Record, [Encoder | Rest]) ->
case Encoder(Record) of
not_implemented ->
try_custom_encoders(Record, Rest);
EncodedData ->
[EncodedData]
end.
encode_data(#dns_rrdata_soa{
mname = Mname,
rname = Rname,
serial = Serial,
refresh = Refresh,
retry = Retry,
expire = Expire,
minimum = Minimum
}) ->
erlang:iolist_to_binary(
io_lib:format("~s. ~s. (~w ~w ~w ~w ~w)", [
Mname, Rname, Serial, Refresh, Retry, Expire, Minimum
])
);
encode_data(#dns_rrdata_ns{dname = Dname}) ->
erlang:iolist_to_binary(io_lib:format("~s.", [Dname]));
encode_data(#dns_rrdata_a{ip = Address}) ->
list_to_binary(inet:ntoa(Address));
encode_data(#dns_rrdata_aaaa{ip = Address}) ->
list_to_binary(inet:ntoa(Address));
encode_data(#dns_rrdata_caa{flags = Flags, tag = Tag, value = Value}) ->
erlang:iolist_to_binary(io_lib:format("~w ~s \"~s\"", [Flags, Tag, Value]));
encode_data(#dns_rrdata_cname{dname = Dname}) ->
erlang:iolist_to_binary(io_lib:format("~s.", [Dname]));
encode_data(#dns_rrdata_mx{preference = Preference, exchange = Dname}) ->
erlang:iolist_to_binary(io_lib:format("~w ~s.", [Preference, Dname]));
encode_data(#dns_rrdata_hinfo{cpu = Cpu, os = Os}) ->
erlang:iolist_to_binary(io_lib:format("~w ~w", [Cpu, Os]));
encode_data(#dns_rrdata_txt{txt = Text}) ->
erlang:iolist_to_binary(io_lib:format("~s", [Text]));
encode_data(#dns_rrdata_sshfp{alg = Alg, fp_type = Fptype, fp = Fp}) ->
erlang:iolist_to_binary(io_lib:format("~w ~w ~s", [Alg, Fptype, Fp]));
encode_data(#dns_rrdata_srv{priority = Priority, weight = Weight, port = Port, target = Dname}) ->
erlang:iolist_to_binary(io_lib:format("~w ~w ~w ~s.", [Priority, Weight, Port, Dname]));
encode_data(#dns_rrdata_naptr{
order = Order,
preference = Preference,
flags = Flags,
services = Services,
regexp = Regexp,
replacement = Replacements
}) ->
erlang:iolist_to_binary(
io_lib:format("~w ~w ~s ~s ~s ~s", [
Order, Preference, Flags, Services, Regexp, Replacements
])
);
encode_data(#dns_rrdata_ds{
keytag = KeyTag,
alg = Alg,
digest_type = DigestType,
digest = Digest
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~s", [KeyTag, Alg, DigestType, Digest])
);
encode_data(#dns_rrdata_cds{
keytag = KeyTag,
alg = Alg,
digest_type = DigestType,
digest = Digest
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~s", [KeyTag, Alg, DigestType, Digest])
);
encode_data(#dns_rrdata_dnskey{
flags = Flags, protocol = Protocol, alg = Alg, public_key = Key, keytag = KeyTag
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~w ~w", [Flags, Protocol, Alg, Key, KeyTag])
);
encode_data(#dns_rrdata_cdnskey{
flags = Flags, protocol = Protocol, alg = Alg, public_key = Key, keytag = KeyTag
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~w ~w", [Flags, Protocol, Alg, Key, KeyTag])
);
encode_data(
#dns_rrdata_rrsig{
type_covered = TypeCovered,
alg = Alg,
labels = Labels,
original_ttl = OriginalTtl,
expiration = Expiration,
inception = Inception,
keytag = KeyTag,
signers_name = SignersName,
signature = Signature
}
) ->
escape_chars(
io_lib:format(
"~w ~w ~w ~w ~w ~w ~w ~w ~s",
[
TypeCovered,
Alg,
Labels,
OriginalTtl,
Expiration,
Inception,
KeyTag,
SignersName,
Signature
]
)
);
encode_data(#dns_rrdata_tlsa{
usage = Usage,
selector = Selector,
matching_type = MatchingType,
certificate = Certificate
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~s", [Usage, Selector, MatchingType, Certificate])
);
encode_data(#dns_rrdata_svcb{
svc_priority = Priority,
target_name = TargetName,
svc_params = SvcParams
}) ->
ParamsStr = encode_svcb_params(SvcParams),
case ParamsStr of
[] ->
erlang:iolist_to_binary(io_lib:format("~w ~s", [Priority, TargetName]));
_ ->
erlang:iolist_to_binary(
io_lib:format("~w ~s~s", [Priority, TargetName, ParamsStr])
)
end;
encode_data(#dns_rrdata_https{
svc_priority = Priority,
target_name = TargetName,
svc_params = SvcParams
}) ->
ParamsStr = encode_svcb_params(SvcParams),
case ParamsStr of
[] ->
erlang:iolist_to_binary(io_lib:format("~w ~s.", [Priority, TargetName]));
_ ->
erlang:iolist_to_binary(
io_lib:format("~w ~s.~s", [Priority, TargetName, ParamsStr])
)
end;
encode_data(#dns_rrdata_openpgpkey{data = Data}) ->
base64:encode(Data);
encode_data(#dns_rrdata_smimea{
usage = Usage,
selector = Selector,
matching_type = MatchingType,
certificate = Certificate
}) ->
escape_chars(
io_lib:format("~w ~w ~w ~s", [Usage, Selector, MatchingType, Certificate])
);
encode_data(#dns_rrdata_uri{priority = Priority, weight = Weight, target = Target}) ->
erlang:iolist_to_binary(io_lib:format("~w ~w ~s", [Priority, Weight, Target]));
encode_data(#dns_rrdata_wallet{data = Data}) ->
base64:encode(Data);
encode_data(#dns_rrdata_eui48{address = Address}) ->
binary:encode_hex(Address);
encode_data(#dns_rrdata_eui64{address = Address}) ->
binary:encode_hex(Address);
encode_data(#dns_rrdata_csync{
soa_serial = Serial,
flags = Flags,
types = Types
}) ->
TypesStr =
case Types of
[] -> "";
_ -> " " ++ string:join([integer_to_list(T) || T <- Types], " ")
end,
erlang:iolist_to_binary(io_lib:format("~w ~w~s", [Serial, Flags, TypesStr]));
encode_data(#dns_rrdata_dsync{
rrtype = Rrtype,
scheme = Scheme,
port = Port,
target = Target
}) ->
erlang:iolist_to_binary(
io_lib:format("~w ~w ~w ~s.", [Rrtype, Scheme, Port, Target])
);
encode_data(#dns_rrdata_zonemd{
serial = Serial,
scheme = Scheme,
algorithm = Algorithm,
hash = Hash
}) ->
HashHex = binary:encode_hex(Hash),
erlang:iolist_to_binary(
io_lib:format("~w ~w ~w ~s", [Serial, Scheme, Algorithm, HashHex])
);
encode_data(Data) ->
?LOG_INFO(#{what => unable_to_encode_rrdata, data => Data}, ?LOG_METADATA),
not_implemented.
%% Helper function to encode SVCB service parameters
-spec encode_svcb_params(dns:svcb_svc_params()) -> iolist().
encode_svcb_params(SvcParams) when is_map(SvcParams) ->
SortedParams = lists:sort(maps:to_list(SvcParams)),
encode_svcb_params(SortedParams, []).
-spec encode_svcb_params([{dns:uint16(), term()}], [iolist()]) -> iolist().
encode_svcb_params([], Acc) ->
lists:reverse(Acc);
encode_svcb_params([{Key, Value} | Rest], Acc) ->
ParamStr =
case {Key, Value} of
{?DNS_SVCB_PARAM_MANDATORY, Keys} when is_list(Keys) ->
KeyNames = [dns_names:svcb_param_name(K) || K <- Keys],
io_lib:format(" mandatory=\"~s\"", [
string:join([binary_to_list(K) || K <- KeyNames], ",")
]);
{?DNS_SVCB_PARAM_ALPN, Protocols} when is_list(Protocols) ->
ProtocolStrs = [binary_to_list(P) || P <- Protocols],
io_lib:format(" alpn=\"~s\"", [string:join(ProtocolStrs, ",")]);
{?DNS_SVCB_PARAM_NO_DEFAULT_ALPN, none} ->
" no-default-alpn";
{?DNS_SVCB_PARAM_PORT, Port} when is_integer(Port) ->
io_lib:format(" port=\"~w\"", [Port]);
{?DNS_SVCB_PARAM_ECH, ECH} when is_binary(ECH) ->
ECHBase64 = base64:encode(ECH),
io_lib:format(" ech=\"~s\"", [ECHBase64]);
{?DNS_SVCB_PARAM_IPV4HINT, IPs} when is_list(IPs) ->
IPStrs = [inet:ntoa(IP) || IP <- IPs],
io_lib:format(" ipv4hint=\"~s\"", [string:join(IPStrs, ",")]);
{?DNS_SVCB_PARAM_IPV6HINT, IPs} when is_list(IPs) ->
IPStrs = [inet:ntoa(IP) || IP <- IPs],
io_lib:format(" ipv6hint=\"~s\"", [string:join(IPStrs, ",")]);
{Key, Value} when is_binary(Value) ->
%% Unknown parameter with binary value
ValueBase64 = encode_quoted_string(Value),
io_lib:format(" key~w=~s", [Key, ValueBase64]);
_ ->
""
end,
encode_svcb_params(Rest, [ParamStr | Acc]).
-spec encode_quoted_string(binary()) -> binary().
encode_quoted_string(Bin) ->
%% Escape backslashes and quotes, then wrap in quotes
Escaped = do_escape_string(Bin, <<$">>),
<<Escaped/binary, $">>.
%% Escape string for zone file format
-spec do_escape_string(binary(), binary()) -> binary().
do_escape_string(<<>>, Acc) ->
Acc;
do_escape_string(<<$\\, Rest/binary>>, Acc) ->
do_escape_string(Rest, <<Acc/binary, "\\\\">>);
do_escape_string(<<$", Rest/binary>>, Acc) ->
do_escape_string(Rest, <<Acc/binary, "\"">>);
do_escape_string(<<C, Rest/binary>>, Acc) when C >= 32, C =< 126 ->
do_escape_string(Rest, <<Acc/binary, C>>);
do_escape_string(<<C, Rest/binary>>, Acc) ->
%% Non-printable, use escape format that matches decoder expectations
%% Note: Using binary format (not octal) to match existing decoder behavior
Escape = list_to_binary(io_lib:format("\\~3..0B", [C])),
do_escape_string(Rest, <<Acc/binary, Escape/binary>>).
escape_chars(IoList) ->
binary:encode_hex(erlang:iolist_to_binary(IoList)).