Packages
erldns
10.3.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_codec.erl
-module(erldns_zone_codec).
-moduledoc """
Encoding and decoding of zone data in JSON format.
To write custom codecs, you need to implement the callbacks exposed by this module,
and register the codec in the configuration.
## Configuration
```erlang
{erldns, [
{zones, #{
codecs => [sample_custom_zone_codec]
}},
]}
```
## Custom codecs
```erlang
-module(sample_custom_zone_codec).
-behaviour(erldns_zone_codec).
-include_lib("dns_erlang/include/dns.hrl").
-include_lib("erldns/include/erldns.hrl").
-export([decode/1, encode/1]).
-define(DNS_TYPE_SAMPLE, 40000).
decode(#{~"name" := Name, ~"type" := ~"SAMPLE", ~"ttl" := Ttl, ~"data" := Data}) ->
#dns_rr{
name = Name,
type = ?DNS_TYPE_SAMPLE,
data = maps:get(~"dname", Data),
ttl = Ttl
};
decode(_) ->
not_implemented.
encode(#dns_rr{name = Name, type = ?DNS_TYPE_SAMPLE, ttl = Ttl, data = Data}) ->
#{
~"name" => Name,
~"type" => ~"SAMPLE",
~"ttl" => Ttl,
~"content" => erlang:iolist_to_binary(io_lib:format("~s", [Data]))
};
encode(_) ->
not_implemented.
```
""".
-behaviour(gen_server).
-include_lib("dns_erlang/include/dns.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("erldns/include/erldns.hrl").
-define(TIMEOUT, 5000).
-export([
build_zone/4,
encode/1,
encode/2,
decode/1,
decode_record/1,
register_codecs/1,
register_codec/1,
list_codecs/0
]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2, terminate/2]).
-type encoder() :: fun((dns:rr()) -> not_implemented | json:encode_value()).
-type decoder() :: fun((dynamic()) -> not_implemented | dns:rr()).
-callback encode(dns:rr()) -> not_implemented | json:encode_value().
-callback decode(json:encode_value()) -> not_implemented | dns:rr().
-export_type([encoder/0, decoder/0]).
-record(state, {
encoders :: [encoder()],
decoders :: [decoder()]
}).
-type state() :: #state{}.
-export_type([state/0]).
-spec build_zone(dns:dname(), binary(), [dns:rr()], [erldns:keyset()]) -> erldns:zone().
build_zone(Name, Version, Records, Keys) ->
NormalizedName = dns_domain:to_lower(Name),
Labels = dns_domain:split(NormalizedName),
Authorities = lists:filter(erldns_records:match_type(?DNS_TYPE_SOA), Records),
#zone{
labels = Labels,
name = NormalizedName,
version = Version,
record_count = length(Records),
authority = Authorities,
records = Records,
keysets = Keys
}.
-doc #{equiv => encode(Zone, #{mode => zone_to_json})}.
-spec encode(erldns:zone()) -> json:encode_value().
encode(Zone) ->
encode(Zone, #{mode => zone_to_json}).
-doc "Takes a zone and turns it into a map.".
-spec encode(erldns:zone(), #{atom() => dynamic()}) -> json:encode_value().
encode(Zone, Opts) ->
{Encoders, _} = list_codecs(),
erldns_zone_encoder:encode(Zone, Opts, Encoders).
-doc "Takes a JSON map and turns it into a zone.".
-spec decode(json:decode_value()) -> erldns:zone().
decode(Zone) ->
{_, Decoders} = list_codecs(),
erldns_zone_decoder:decode(Zone, Decoders).
-doc "Takes a JSON record and turns it into a `t:dns:rr/0`.".
-spec decode_record(#{binary() => json:decode_value()}) -> not_implemented | dns:rr().
decode_record(Record) ->
{_, Decoders} = list_codecs(),
erldns_zone_decoder:decode_record(Record, Decoders).
-doc "Register a custom parser module.".
-spec register_codec(module()) -> ok.
register_codec(Module) when is_atom(Module) ->
register_codecs([Module]).
-doc "Register a list of custom parser modules.".
-spec register_codecs([module()]) -> ok.
register_codecs(Modules) when is_list(Modules) ->
?LOG_NOTICE(
#{what => registering_custom_parsers, parsers => Modules},
#{domain => [erldns, zones]}
),
gen_server:call(?MODULE, {register_codecs, Modules}, ?TIMEOUT).
-doc "Get the list of registered zone parsers.".
-spec list_codecs() -> {[encoder()], [decoder()]}.
list_codecs() ->
persistent_term:get(?MODULE, {[], []}).
% Internal API
-doc false.
-spec start_link() -> gen_server:start_ret().
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, noargs, [{hibernate_after, 0}]).
-doc false.
-spec init(noargs) -> {ok, state()}.
init(noargs) ->
process_flag(trap_exit, true),
{Encoders, Decoders} = prepare_codecs(),
persistent_term:put(?MODULE, {Encoders, Decoders}),
{ok, #state{encoders = Encoders, decoders = Decoders}}.
-doc false.
-spec handle_call(dynamic(), gen_server:from(), state()) ->
{reply, dynamic(), state()}.
handle_call({register_codecs, Modules}, _From, State) ->
{NewEncoders, NewDecoders} = prepare_codecs(#{codecs => Modules}),
Encoders = State#state.encoders ++ NewEncoders,
Decoders = State#state.decoders ++ NewDecoders,
persistent_term:put(?MODULE, {Encoders, Decoders}),
{reply, ok, #state{encoders = Encoders, decoders = Decoders}};
handle_call(Call, From, State) ->
?LOG_INFO(#{what => unexpected_call, from => From, call => Call}, #{domain => [erldns, zones]}),
{reply, not_implemented, State}.
-doc false.
-spec handle_cast(dynamic(), state()) -> {noreply, state()}.
handle_cast(Cast, State) ->
?LOG_INFO(#{what => unexpected_cast, cast => Cast}, #{domain => [erldns, zones]}),
{noreply, State}.
-doc false.
-spec terminate(term(), state()) -> term().
terminate(_, _) ->
persistent_term:erase(?MODULE).
-spec prepare_codecs() -> {[encoder()], [decoder()]}.
prepare_codecs() ->
ZonesConfig = application:get_env(erldns, zones, #{}),
prepare_codecs(ZonesConfig).
-spec prepare_codecs(erldns_zones:config()) -> {[encoder()], [decoder()]}.
prepare_codecs(#{codecs := Modules}) ->
lists:foldr(
fun(Module, {AccEncoders, AccDecoders}) ->
maybe
{module, Module} ?= code:ensure_loaded(Module),
true ?= erlang:function_exported(Module, encode, 1),
true ?= erlang:function_exported(Module, decode, 1),
Encoders = [fun Module:encode/1 | AccEncoders],
Decoders = [fun Module:decode/1 | AccDecoders],
{Encoders, Decoders}
else
{error, Reason} ->
erlang:error({badcodec, {module, Reason}});
false ->
erlang:error({badcodec, {module_does_not_export_call, Module}})
end
end,
{[], []},
Modules
);
prepare_codecs(#{}) ->
{[], []}.