Packages
erldns
10.5.6
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/erldns_handler.erl
-module(erldns_handler).
-moduledoc """
The module that handles the resolution of a single DNS question.
The meat of the resolution occurs in erldns_resolver:resolve/3
## Configuration
```erlang
{erldns, [
{packet_handlers, [
{my_custom_handler_module, [?DNS_TYPE_A, ?DNS_TYPE_AAAA], 3}
]},
]}
```
Record types can be given in their integer codes or binary representations,
meaning, the following are equivalent:
```erlang
{my_custom_handler_module, [?DNS_TYPE_A, ?DNS_TYPE_AAAA], 3}
...
{my_custom_handler_module, [?DNS_TYPE_A, ~"AAAA"], 3}
...
{my_custom_handler_module, [~"A", ~"AAAA"], 3}
```
The minimum supported version is `2`.
Version 2's handler signature is
```erlang
handle(dns:message(), dns:labels(), dns:type(), [dns:rr()]) -> [dns:rr()]
```.
Version 3's handler signature is
```erlang
handle(dns:dname(), dns:type(), [dns:rr()], dns:message()) -> [dns:rr()].
```.
""".
-behaviour(gen_server).
-include_lib("kernel/include/logger.hrl").
-include_lib("dns_erlang/include/dns.hrl").
-define(MINIMUM_HANDLER_VERSION, 2).
-define(DEFAULT_HANDLER_VERSION, 3).
-define(TIMEOUT, 5000).
-export([
register_handler/2,
register_handler/3,
get_versioned_handlers/0,
call_filters/1,
call_handlers/4,
call_map_nsec_rr_types/1,
call_map_nsec_rr_types/2,
%% Standalone NSEC type mapper registration (for pipeline modules)
register_nsec_type_mapper/2,
unregister_nsec_type_mapper/2
]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2, terminate/2]).
-record(handlers_state, {
handlers = [] :: [versioned_handler()],
mappers = [] :: [nsec_type_mapper()]
}).
-opaque state() :: #handlers_state{}.
-type versioned_handler() :: {
fun((dns:message(), dns:labels(), dns:type(), [dns:rr()]) -> [dns:rr()]),
fun(([dns:rr()]) -> [dns:rr()]),
fun((dns:type(), dns:type()) -> [dns:type()]),
module(),
[dns:type()],
integer()
}.
-type handler() :: {module(), [dns:type()]}.
-type handler_config() :: {module(), [dns:type()], integer()}.
-type nsec_type_mapper() :: {[dns:type()], fun((dns:type(), dns:type()) -> [dns:type()])}.
-export_type([state/0, versioned_handler/0, handler/0, handler_config/0, nsec_type_mapper/0]).
-doc "Filter the given record set, returning replacement records.".
-callback filter([dns:rr()]) -> [dns:rr()].
-doc "Filter out records not related to the given handler".
-callback handle(dns:message(), dns:labels(), dns:type(), [dns:rr()]) -> [dns:rr()].
-doc "Map handler's record types to NSEC bit types.".
-callback nsec_rr_type_mapper(dns:type(), dns:type()) -> [dns:type()].
-doc "Register a record handler with the default version of 1".
-spec register_handler([dns:type()], module()) -> ok.
register_handler(RecordTypes, Module) ->
register_handler(RecordTypes, Module, ?DEFAULT_HANDLER_VERSION).
-doc "Register a record handler with version".
-spec register_handler([dns:type()], module(), integer()) -> ok.
register_handler(RecordTypes, Module, Version) ->
gen_server:call(?MODULE, {register_handler, {Module, RecordTypes, Version}}, ?TIMEOUT).
-doc "Get all registered handlers along with the DNS types they handle and associated versions".
-spec get_versioned_handlers() -> [versioned_handler()].
get_versioned_handlers() ->
{Handlers, _Mappers} = persistent_term:get(?MODULE, {[], []}),
Handlers.
-doc "Filter records through registered handlers.".
-spec call_filters([dns:rr()]) -> [dns:rr()].
call_filters(Records) ->
filter_records(Records, get_versioned_handlers()).
filter_records(Records, []) ->
Records;
filter_records(Records, [{_, Filter, _, _, _, _} | Rest]) ->
filter_records(Filter(Records), Rest).
-doc "Call all registered handlers.".
-spec call_handlers(dns:message(), dns:labels(), dns:type(), [dns:rr()]) -> [dns:rr()].
call_handlers(Message, QLabels, QType, Records) ->
Handlers = get_versioned_handlers(),
lists:flatmap(call_handlers_fun(Message, QLabels, QType, Records), Handlers).
-spec call_handlers_fun(dns:message(), dns:labels(), dns:type(), [dns:rr()]) ->
fun((versioned_handler()) -> [dns:rr()]).
call_handlers_fun(Message, QLabels, ?DNS_TYPE_ANY, Records) ->
fun
({Handler, _, _, _, _, ?MINIMUM_HANDLER_VERSION}) ->
Handler(dns_domain:join(QLabels), ?DNS_TYPE_ANY, Records, Message);
({Handler, _, _, _, _, ?DEFAULT_HANDLER_VERSION}) ->
Handler(Message, QLabels, ?DNS_TYPE_ANY, Records)
end;
call_handlers_fun(Message, QLabels, QType, Records) ->
fun
({Handler, _, _, _, Types, ?MINIMUM_HANDLER_VERSION}) ->
case lists:member(QType, Types) of
true -> Handler(dns_domain:join(QLabels), QType, Records, Message);
false -> []
end;
({Handler, _, _, _, Types, ?DEFAULT_HANDLER_VERSION}) ->
case lists:member(QType, Types) of
true -> Handler(Message, QLabels, QType, Records);
false -> []
end
end.
-spec call_map_nsec_rr_types([dns:type()]) -> [dns:type()].
call_map_nsec_rr_types(Types) ->
Handlers = get_versioned_handlers(),
Mappers = get_nsec_type_mappers(),
MappedTypes = lists:flatmap(
fun(Type) ->
map_single_type_arity1(Type, Handlers, Mappers)
end,
Types
),
lists:usort(MappedTypes).
-spec call_map_nsec_rr_types(dns:type(), [dns:type()]) -> [dns:type()].
call_map_nsec_rr_types(QType, Types) ->
Handlers = get_versioned_handlers(),
Mappers = get_nsec_type_mappers(),
MappedTypes = map_nsec_rr_types(QType, Types, Handlers, Mappers),
lists:usort(MappedTypes).
%% Map a single type using arity-1 mapper (for call_map_nsec_rr_types/1)
-spec map_single_type_arity1(dns:type(), [versioned_handler()], [nsec_type_mapper()]) ->
[dns:type()].
map_single_type_arity1(Type, Handlers, Mappers) ->
%% First check versioned handlers
case lists:keyfind([Type], 5, Handlers) of
{_, _, _, M, _, _} ->
M:nsec_rr_type_mapper(Type);
false ->
%% Check standalone mappers
case find_standalone_mapper(Type, Mappers) of
{ok, MapperFun} ->
MapperFun(Type, Type);
not_found ->
[Type]
end
end.
-spec map_nsec_rr_types(dns:type(), [dns:type()], [versioned_handler()], [nsec_type_mapper()]) ->
[dns:type()].
map_nsec_rr_types(_QType, Types, [], []) ->
Types;
map_nsec_rr_types(QType, Types, Handlers, Mappers) ->
lists:flatmap(
fun(Type) ->
map_single_type(Type, QType, Handlers, Mappers)
end,
Types
).
%% Map a single type using arity-2 mapper (for call_map_nsec_rr_types/2)
-spec map_single_type(dns:type(), dns:type(), [versioned_handler()], [nsec_type_mapper()]) ->
[dns:type()].
map_single_type(Type, QType, Handlers, Mappers) ->
%% First check versioned handlers
case lists:keyfind([Type], 5, Handlers) of
{_, _, Mapper, _, _, _} ->
Mapper(Type, QType);
false ->
%% Check standalone mappers
case find_standalone_mapper(Type, Mappers) of
{ok, MapperFun} ->
MapperFun(Type, QType);
not_found ->
[Type]
end
end.
-spec find_standalone_mapper(dns:type(), [nsec_type_mapper()]) ->
{ok, fun((dns:type(), dns:type()) -> [dns:type()])} | not_found.
find_standalone_mapper(_Type, []) ->
not_found;
find_standalone_mapper(Type, [{RecordTypes, MapperFun} | Rest]) ->
case lists:member(Type, RecordTypes) of
true -> {ok, MapperFun};
false -> find_standalone_mapper(Type, Rest)
end.
-doc "Start the handler registry process".
-spec start_link() -> gen_server:start_ret().
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, noargs, [{hibernate_after, 0}]).
% gen_server callbacks
-doc false.
-spec init(noargs) -> {ok, state()}.
init(noargs) ->
process_flag(trap_exit, true),
Handlers = prepare_handlers(),
persistent_term:put(?MODULE, {Handlers, []}),
{ok, #handlers_state{handlers = Handlers, mappers = []}}.
-doc false.
-spec handle_call
({register_handler, {module(), [dns:type()], integer()}}, gen_server:from(), state()) ->
{reply, ok, state()};
(
{register_nsec_type_mapper, [dns:type()], fun((dns:type(), dns:type()) -> [dns:type()])},
gen_server:from(),
state()
) ->
{reply, ok, state()};
(
{unregister_nsec_type_mapper, [dns:type()], fun((dns:type(), dns:type()) -> [dns:type()])},
gen_server:from(),
state()
) ->
{reply, ok, state()};
(dynamic(), gen_server:from(), state()) ->
{reply, not_implemented, state()}.
handle_call({register_handler, Handler}, _, State) ->
NewHandlers = prepare_handlers([Handler], State#handlers_state.handlers),
persistent_term:put(?MODULE, {NewHandlers, State#handlers_state.mappers}),
{reply, ok, State#handlers_state{handlers = NewHandlers}};
handle_call({register_nsec_type_mapper, RecordTypes, MapperFun}, _, State) ->
NewMappers = [{RecordTypes, MapperFun} | State#handlers_state.mappers],
persistent_term:put(?MODULE, {State#handlers_state.handlers, NewMappers}),
{reply, ok, State#handlers_state{mappers = NewMappers}};
handle_call({unregister_nsec_type_mapper, RecordTypes, MapperFun}, _, State) ->
NewMappers = lists:delete({RecordTypes, MapperFun}, State#handlers_state.mappers),
persistent_term:put(?MODULE, {State#handlers_state.handlers, NewMappers}),
{reply, ok, State#handlers_state{mappers = NewMappers}};
handle_call(_, _, State) ->
{reply, not_implemented, State}.
-doc false.
-spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast(_, State) ->
{noreply, State}.
-doc false.
-spec terminate(term(), state()) -> term().
terminate(_, _) ->
persistent_term:erase(?MODULE).
-spec get_nsec_type_mappers() -> [nsec_type_mapper()].
get_nsec_type_mappers() ->
{_Handlers, Mappers} = persistent_term:get(?MODULE, {[], []}),
Mappers.
%% NSEC Type Mapper Registry
%% Allows pipeline modules to register type mappers without implementing full handler interface.
-doc """
Register a standalone NSEC type mapper for specific record types.
This allows pipeline modules to provide NSEC type mapping without implementing
the full handler interface (handle/4, filter/1).
Example:
```erlang
erldns_handler:register_nsec_type_mapper([30003], fun my_module:nsec_rr_type_mapper/2)
```
""".
-spec register_nsec_type_mapper([dns:type()], fun((dns:type(), dns:type()) -> [dns:type()])) -> ok.
register_nsec_type_mapper(RecordTypes, MapperFun) when is_function(MapperFun, 2) ->
gen_server:call(?MODULE, {register_nsec_type_mapper, RecordTypes, MapperFun}, ?TIMEOUT).
-doc "Unregister NSEC type mappers for specific record types.".
-spec unregister_nsec_type_mapper([dns:type()], fun((dns:type(), dns:type()) -> [dns:type()])) ->
ok.
unregister_nsec_type_mapper(RecordTypes, MapperFun) ->
gen_server:call(?MODULE, {unregister_nsec_type_mapper, RecordTypes, MapperFun}, ?TIMEOUT).
-spec prepare_handlers() -> [versioned_handler()].
prepare_handlers() ->
Handlers = application:get_env(erldns, packet_handlers, []),
prepare_handlers(Handlers, []).
-spec prepare_handlers([dynamic()], [versioned_handler()]) -> [versioned_handler()].
prepare_handlers([], Acc) ->
lists:reverse(Acc);
prepare_handlers([{Module, RecordTypes, Version} | Rest], Acc) ->
?LOG_INFO(
#{what => registered_handler, module => Module, types => RecordTypes, version => Version},
#{domain => [erldns, pipeline]}
),
maybe
{module, Module} ?= code:ensure_loaded(Module),
true ?= erlang:function_exported(Module, handle, 4),
true ?= erlang:function_exported(Module, filter, 1),
true ?= erlang:function_exported(Module, nsec_rr_type_mapper, 2),
true ?= Version >= ?MINIMUM_HANDLER_VERSION orelse {error, {version, Version}},
{ok, RecordTypesNums} ?= ensure_valid_record_types(RecordTypes, []),
Prepared = {
fun Module:handle/4,
fun Module:filter/1,
fun Module:nsec_rr_type_mapper/2,
Module,
RecordTypesNums,
Version
},
prepare_handlers(Rest, [Prepared | Acc])
else
{error, Reason} ->
erlang:error({badhandler, Module, Reason});
false ->
erlang:error({badhandler, Module, module_does_not_export_call})
end.
ensure_valid_record_types([], Acc) ->
{ok, lists:reverse(Acc)};
ensure_valid_record_types([Type | Rest], Acc) when is_integer(Type) ->
ensure_valid_record_types(Rest, [Type | Acc]);
ensure_valid_record_types([TypeBin | Rest], Acc) when is_binary(TypeBin) ->
case dns_names:name_type(TypeBin) of
undefined ->
{error, {record_type, TypeBin}};
Type ->
ensure_valid_record_types(Rest, [Type | Acc])
end.