Packages
erldns
4.1.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/erldns_handler.erl
%% Copyright (c) 2012-2020, DNSimple Corporation
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% @doc The module that handles the resolution of a single DNS question.
%%
%% The meat of the resolution occurs in erldns_resolver:resolve/3
-module(erldns_handler).
-behavior(gen_server).
-include_lib("dns_erlang/include/dns.hrl").
-include("erldns.hrl").
-define(DEFAULT_HANDLER_VERSION, 1).
-export([
start_link/0,
register_handler/2,
register_handler/3,
get_handlers/0,
get_versioned_handlers/0,
handle/2
]).
-export([do_handle/2]).
% Gen server hooks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
% Internal API
-export([handle_message/2]).
-record(state, {handlers}).
%% @doc Start the handler registry process.
-spec start_link() -> any().
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% @doc Register a record handler.
-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, RecordTypes, Module, Version}).
%% @doc Get all registered handlers along with the DNS types they handle
-spec get_handlers() -> [{module(), [dns:type()]}].
get_handlers() ->
Handlers = gen_server:call(?MODULE, {get_handlers}),
% return only Version 1 handlers
% strip version information for Version handlers
[erlang:delete_element(3, X) || X <- Handlers, element(3, X) =:= ?DEFAULT_HANDLER_VERSION].
%% @doc Get all registered handlers along with the DNS types they handle and associated versions
-spec get_versioned_handlers() -> [{module(), [dns:type()], integer()}].
get_versioned_handlers() ->
gen_server:call(?MODULE, {get_handlers}).
% gen_server callbacks
init([]) ->
{ok, #state{handlers = []}}.
handle_call({register_handler, RecordTypes, Module}, _, State) ->
lager:info("Registered handler (module: ~p, types: ~p)", [Module, RecordTypes]),
{reply, ok, State#state{handlers = State#state.handlers ++ [{Module, RecordTypes, 1}]}};
handle_call({register_handler, RecordTypes, Module, Version}, _, State) ->
lager:info("Registered handler (module: ~p, types: ~p, version: ~p)", [Module, RecordTypes, Version]),
{reply, ok, State#state{handlers = State#state.handlers ++ [{Module, RecordTypes, Version}]}};
handle_call({get_handlers}, _, State) ->
{reply, State#state.handlers, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info(_, State) ->
{noreply, State}.
terminate(_, _) ->
erldns_storage:delete_table(handler_registry),
ok.
code_change(_PreviousVersion, State, _Extra) ->
{ok, State}.
%% If the message has trailing garbage just throw the garbage away and continue
%% trying to process the message.
handle({trailing_garbage, Message, _}, Context) ->
handle(Message, Context);
%% Handle the message, checking to see if it is throttled.
handle(Message, Context = {_, Host}) when is_record(Message, dns_message) ->
handle(Message, Host, erldns_query_throttle:throttle(Message, Context));
%% The message was bad so just return it.
%% TODO: consider just throwing away the message
handle(Message, {_, Host}) ->
lager:error("Received a bad message (module: ~p, event: ~p, message: ~p, host: ~p)", [?MODULE, bad_message, Message, Host]),
Message.
%% We throttle ANY queries to discourage use of our authoritative name servers
%% for reflection attacks.
%%
%% Note: this should probably be changed to return the original packet without
%% any answer data and with TC bit set to 1.
handle(Message, Host, {throttled, Host, _ReqCount}) ->
folsom_metrics:notify({request_throttled_counter, {inc, 1}}),
folsom_metrics:notify({request_throttled_meter, 1}),
Message#dns_message{
tc = true,
aa = true,
rc = ?DNS_RCODE_NOERROR
};
%% Message was not throttled, so handle it, then do EDNS handling, optionally
%% append the SOA record if it is a zone transfer and complete the response
%% by filling out count-related header fields.
handle(Message, Host, _) ->
erldns_events:notify({?MODULE, start_handle, [{host, Host}, {message, Message}]}),
Response = folsom_metrics:histogram_timed_update(request_handled_histogram, ?MODULE, do_handle, [Message, Host]),
erldns_events:notify({?MODULE, end_handle, [{host, Host}, {message, Message}, {response, Response}]}),
Response.
do_handle(Message, Host) ->
NewMessage = handle_message(Message, Host),
complete_response(erldns_axfr:optionally_append_soa(NewMessage)).
%% Handle the message by hitting the packet cache and either
%% using the cached packet or continuing with the lookup process.
%%
%% If the cache is missed, then the SOA (Start of Authority) is discovered here.
handle_message(Message, Host) ->
case erldns_packet_cache:get({Message#dns_message.questions, Message#dns_message.additional}, Host) of
{ok, CachedResponse} ->
erldns_events:notify({?MODULE, packet_cache_hit, [{host, Host}, {message, Message}]}),
CachedResponse#dns_message{id = Message#dns_message.id};
{error, Reason} ->
erldns_events:notify({?MODULE, packet_cache_miss, [{reason, Reason}, {host, Host}, {message, Message}]}),
% SOA lookup
handle_packet_cache_miss(Message, get_authority(Message), Host)
end.
%% If the packet is not in the cache and we are not authoritative (because there
%% is no SOA record for this zone), then answer immediately setting the AA flag to false.
%% If erldns is configured to use root hints then those will be added to the response.
-spec handle_packet_cache_miss(Message :: dns:message(), AuthorityRecords :: dns:authority(), Host :: dns:ip()) -> dns:message().
handle_packet_cache_miss(Message, [], _Host) ->
case erldns_config:use_root_hints() of
true ->
{Authority, Additional} = erldns_records:root_hints(),
Message#dns_message{
aa = false,
rc = ?DNS_RCODE_REFUSED,
authority = Authority,
additional = Additional
};
_ ->
Message#dns_message{aa = false, rc = ?DNS_RCODE_REFUSED}
end;
%% The packet is not in the cache yet we are authoritative, so try to resolve
%% the request. This is the point the request moves on to the erldns_resolver
%% module.
handle_packet_cache_miss(Message, AuthorityRecords, Host) ->
safe_handle_packet_cache_miss(Message#dns_message{ra = false}, AuthorityRecords, Host).
-spec safe_handle_packet_cache_miss(Message :: dns:message(), AuthorityRecords :: dns:authority(), Host :: dns:ip()) -> dns:message().
safe_handle_packet_cache_miss(Message, AuthorityRecords, Host) ->
case application:get_env(erldns, catch_exceptions) of
{ok, false} ->
Response = erldns_resolver:resolve(Message, AuthorityRecords, Host),
maybe_cache_packet(Response, Response#dns_message.aa);
_ ->
try erldns_resolver:resolve(Message, AuthorityRecords, Host) of
Response ->
maybe_cache_packet(Response, Response#dns_message.aa)
catch
Exception:Reason:Stacktrace ->
% lager:error("Error answering request (module: ~p, event: ~p, exception: ~p, reason: ~p, message: ~p, stacktrace: "
% "~p)",
% [?MODULE, resolve_error, Exception, Reason, Message, Stacktrace]),
erldns_events:notify({?MODULE, resolve_error, {Exception, Reason, Message, Stacktrace}}),
RCode =
case Reason of
{error, rcode, ?DNS_RCODE_SERVFAIL} -> ?DNS_RCODE_SERVFAIL;
{error, rcode, ?DNS_RCODE_NXDOMAIN} -> ?DNS_RCODE_NXDOMAIN;
{error, rcode, ?DNS_RCODE_REFUSED} -> ?DNS_RCODE_REFUSED;
_ -> ?DNS_RCODE_SERVFAIL
end,
Message#dns_message{aa = false, rc = RCode}
end
end.
%% We are authoritative so cache the packet and return the message.
maybe_cache_packet(Message, true) ->
erldns_packet_cache:put({Message#dns_message.questions, Message#dns_message.additional}, Message),
Message;
%% We are not authoritative so just return the message.
maybe_cache_packet(Message, false) ->
Message.
%% Get the SOA authority for the current query.
get_authority(MessageOrName) ->
case erldns_zone_cache:get_authority(MessageOrName) of
{ok, Authority} ->
Authority;
{error, _} ->
[]
end.
%% Update the message counts and set the QR flag to true.
complete_response(Message) ->
notify_empty_response(Message#dns_message{
anc = length(Message#dns_message.answers),
auc = length(Message#dns_message.authority),
adc = length(Message#dns_message.additional),
qr = true
}).
notify_empty_response(Message) ->
case {Message#dns_message.rc, Message#dns_message.anc + Message#dns_message.auc + Message#dns_message.adc} of
{?DNS_RCODE_REFUSED, _} ->
erldns_events:notify({?MODULE, refused_response, Message#dns_message.questions}),
Message;
{_, 0} ->
lager:info("Empty response (module: ~p, event: ~p, message: ~p)", [?MODULE, empty_response, Message]),
erldns_events:notify({?MODULE, empty_response, Message}),
Message;
_ ->
Message
end.