Packages
erldns
3.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/erldns_worker.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 Worker module that asynchronously accepts a single DNS packet and
%% hands it to a worker process that has a set timeout.
-module(erldns_worker).
-include_lib("dns_erlang/include/dns.hrl").
-include_lib("opentelemetry_api/include/otel_tracer.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-behaviour(gen_server).
-export([start_link/1]).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {worker_process_sup, worker_process}).
erlang_proc_message_queue_len() ->
case process_info(self(), message_queue_len) of
undefined -> <<"undefined">>;
{message_queue_len, Count} -> Count
end.
start_link(Args) ->
gen_server:start_link(?MODULE, Args, []).
init([WorkerId]) ->
{ok, WorkerProcessSup} = erldns_worker_process_sup:start_link([WorkerId]),
WorkerProcess = lists:last(supervisor:which_children(WorkerProcessSup)),
{ok, #state{worker_process_sup = WorkerProcessSup, worker_process = WorkerProcess}}.
handle_call(_Request, From, State) ->
lager:debug("Received unexpected call (from: ~p)", [From]),
{reply, ok, State}.
handle_cast({tcp_query, Socket, Bin}, State) ->
?with_span(
<<"erldns_tcp_worker">>,
#{},
fun(SpanCtx) ->
case handle_tcp_dns_query(Socket, Bin, SpanCtx, {State#state.worker_process_sup, State#state.worker_process}) of
ok ->
{noreply, State};
{error, timeout, NewWorkerPid} ->
{Id, _, Type, Modules} = State#state.worker_process,
{noreply, State#state{worker_process = {Id, NewWorkerPid, Type, Modules}}};
Error ->
lager:error("Error handling TCP query (module: ~p, event: ~p, error: ~p)", [?MODULE, handle_tcp_query_error, Error]),
{noreply, State}
end
end
);
handle_cast({udp_query, Socket, Host, Port, Bin}, State) ->
?with_span(
<<"erldns_udp_worker">>,
#{},
fun(_SpanCtx) ->
?set_attributes([{host, Host}, {port, Port}, {worker_process, State#state.worker_process}]),
?set_attributes([
{erlang_port_count, erlang:system_info(port_count)},
{erlang_proc_count, erlang:system_info(process_count)},
{erlang_run_queue, erlang:statistics(run_queue)},
{erlang_proc_message_queue_len, erlang_proc_message_queue_len()}
]),
case
handle_udp_dns_query(Socket, Host, Port, Bin, ?current_span_ctx, {
State#state.worker_process_sup, State#state.worker_process
})
of
ok ->
?set_attributes([{status, <<"ok">>}]),
{noreply, State};
{error, timeout, NewWorkerPid} ->
?set_attributes([{status, <<"timeout">>}]),
{Id, _, Type, Modules} = State#state.worker_process,
{noreply, State#state{worker_process = {Id, NewWorkerPid, Type, Modules}}};
Error ->
?set_attributes([{status, <<"error">>}]),
lager:error("Error handling UDP query (module: ~p, event: ~p, error: ~p)", [?MODULE, handle_udp_query_error, Error]),
{noreply, State}
end
end
);
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @doc Handle DNS query that comes in over TCP
-spec handle_tcp_dns_query(gen_tcp:socket(), iodata(), otel_tracer:tracer_ctx(), {pid(), term()}) ->
ok | {error, timeout} | {error, timeout, pid()}.
handle_tcp_dns_query(Socket, <<_Len:16, Bin/binary>>, SpanCtx, {WorkerProcessSup, WorkerProcess}) ->
?set_current_span(SpanCtx),
?with_span(
<<"handle_tcp_dns_query">>,
#{},
fun(_SpanCtx) ->
case inet:peername(Socket) of
{ok, {Address, _Port}} ->
try
erldns_events:notify({?MODULE, start_tcp, [{host, Address}]}),
case Bin of
<<>> ->
ok;
_ ->
case erldns_decoder:decode_message(Bin) of
{trailing_garbage, DecodedMessage, TrailingGarbage} ->
Query = lists:last(DecodedMessage#dns_message.questions),
?set_attributes([
{status, <<"trailing_garbage">>},
{qr, DecodedMessage#dns_message.qr},
{rd, DecodedMessage#dns_message.rd},
{ad, DecodedMessage#dns_message.ad},
{qname, Query#dns_query.name},
{qtype, dns:type_name(Query#dns_query.type)}
]),
lager:info(
"Decoded message included trailing garbage (module: ~p, event: ~p, message: ~p, garbage: ~p)",
[?MODULE, decode_message_trailing_garbage, DecodedMessage, TrailingGarbage]
),
% erldns_events:notify({?MODULE, decode_message_trailing_garbage, {DecodedMessage, TrailingGarbage}}),
handle_decoded_tcp_message(
DecodedMessage, Socket, Address, SpanCtx, {WorkerProcessSup, WorkerProcess}
);
{Error, Message, _} ->
otel_span:set_status(SpanCtx, #status{code = error, message = <<"Error decoding message">>}),
lager:error(
"Error decoding message (module: ~p, event: ~p, error: ~p, message: ~p)",
[?MODULE, decode_message_error, Error, Message]
),
% erldns_events:notify({?MODULE, decode_message_error, {Error, Message}}),
ok;
DecodedMessage ->
Query = lists:last(DecodedMessage#dns_message.questions),
?set_attributes([
{status, <<"ok">>},
{qr, DecodedMessage#dns_message.qr},
{rd, DecodedMessage#dns_message.rd},
{ad, DecodedMessage#dns_message.ad},
{qname, Query#dns_query.name},
{qtype, dns:type_name(Query#dns_query.type)}
]),
handle_decoded_tcp_message(
DecodedMessage, Socket, Address, SpanCtx, {WorkerProcessSup, WorkerProcess}
)
end
end
of
Result ->
erldns_events:notify({?MODULE, end_tcp, [{host, Address}]}),
Result
catch
Exception:Reason:Stacktrace ->
otel_span:set_status(SpanCtx, #status{code = error, message = <<"">>}),
otel_span:record_exception(SpanCtx, Exception, Reason, Stacktrace, []),
{error, Exception, Reason}
after
gen_tcp:close(Socket)
end;
{error, Reason} ->
lager:debug("Notifying error reason: ~p", [Reason]),
erldns_events:notify({?MODULE, tcp_error, Reason})
end
end
);
handle_tcp_dns_query(Socket, BadPacket, SpanCtx, _) ->
?set_current_span(SpanCtx),
?with_span(
<<"handle_tcp_dns_query">>,
#{},
fun(_SpanCtx) ->
?set_attributes([{status, <<"bad_packet">>}]),
lager:error("Received bad packet (module: ~p, event: ~p, protocol: ~p, packet: ~p)", [?MODULE, bad_packet, tcp, BadPacket]),
% erldns_events:notify({?MODULE, bad_packet, {tcp, BadPacket}}),
gen_tcp:close(Socket)
end
).
handle_decoded_tcp_message(DecodedMessage, Socket, Address, SpanCtx, {WorkerProcessSup, {WorkerProcessId, WorkerProcessPid, _, _}}) ->
?set_current_span(SpanCtx),
?with_span(
<<"handle_decoded_tcp_message">>,
#{},
fun(_SpanCtx) ->
case DecodedMessage#dns_message.qr of
false ->
try
gen_server:call(
WorkerProcessPid,
{process, DecodedMessage, Socket, {tcp, Address}, SpanCtx},
_Timeout = erldns_config:ingress_tcp_request_timeout()
)
of
_ ->
ok
catch
exit:{timeout, _} ->
?set_attributes([{status, <<"timeout">>}]),
erldns_events:notify({?MODULE, timeout}),
handle_timeout(WorkerProcessSup, WorkerProcessId);
Error:Reason ->
?set_attributes([{status, <<"error">>}]),
lager:error(
"Worker process crashed (module: ~p, event: ~p, protocol: ~p, error: ~p, reason: ~p, message: ~p)",
[?MODULE, process_crashed, tcp, Error, Reason, DecodedMessage]
),
{error, {Error, Reason}}
end;
true ->
{error, not_a_question}
end
end
).
%% @doc Handle DNS query that comes in over UDP
-spec handle_udp_dns_query(gen_udp:socket(), gen_udp:ip(), inet:port_number(), binary(), otel_tracer:tracer_ctx(), {pid(), term()}) ->
ok | {error, not_owner | timeout | inet:posix() | atom()} | {error, timeout, pid()}.
handle_udp_dns_query(Socket, Host, Port, Bin, SpanCtx, {WorkerProcessSup, WorkerProcess}) ->
erldns_events:notify({?MODULE, start_udp, [{host, Host}]}),
?set_current_span(SpanCtx),
Result = ?with_span(
<<"handle_udp_dns_query">>,
#{},
fun(_SpanCtx) ->
case erldns_decoder:decode_message(Bin) of
{trailing_garbage, DecodedMessage, TrailingGarbage} ->
?set_attributes([{status, <<"trailing_garbage">>}]),
lager:info(
"Decoded message included trailing garbage (module: ~p, event: ~p, message: ~p, garbage: ~p)",
[?MODULE, decode_message_trailing_garbage, DecodedMessage, TrailingGarbage]
),
%erldns_events:notify({?MODULE, decode_message_trailing_garbage, {DecodedMessage, TrailingGarbage}}),
handle_decoded_udp_message(DecodedMessage, Socket, Host, Port, SpanCtx, {WorkerProcessSup, WorkerProcess});
{Error, Message, _} ->
?set_attributes([{status, <<"error">>}]),
lager:error("Error decoding message (module: ~p, event: ~p, error: ~p, message: ~p)", [
?MODULE, decode_message_error, Error, Message
]),
% erldns_events:notify({?MODULE, decode_message_error, {Error, Message}}),
ok;
DecodedMessage ->
Query = lists:last(DecodedMessage#dns_message.questions),
?set_attributes([
{status, <<"ok">>},
{qr, DecodedMessage#dns_message.qr},
{rd, DecodedMessage#dns_message.rd},
{ad, DecodedMessage#dns_message.ad},
{qname, Query#dns_query.name},
{qtype, dns:type_name(Query#dns_query.type)}
]),
handle_decoded_udp_message(DecodedMessage, Socket, Host, Port, SpanCtx, {WorkerProcessSup, WorkerProcess})
end
end
),
erldns_events:notify({?MODULE, end_udp, [{host, Host}]}),
Result.
-spec handle_decoded_udp_message(dns:message(), gen_udp:socket(), gen_udp:ip(), inet:port_number(), otel_tracer:tracer_ctx(), {
pid(), term()
}) ->
ok | {error, not_owner | timeout | inet:posix() | atom()} | {error, timeout, term()}.
handle_decoded_udp_message(DecodedMessage, Socket, Host, Port, SpanCtx, {WorkerProcessSup, {WorkerProcessId, WorkerProcessPid, _, _}}) ->
?set_current_span(SpanCtx),
?with_span(
<<"handle_decoded_udp_message">>,
#{},
fun(_SpanCtx) ->
case DecodedMessage#dns_message.qr of
false ->
try
gen_server:call(
WorkerProcessPid,
{process, DecodedMessage, Socket, Port, {udp, Host}, SpanCtx},
_Timeout = erldns_config:ingress_udp_request_timeout()
)
of
_ ->
ok
catch
exit:{timeout, _} ->
?set_attributes([{status, <<"timeout">>}]),
lager:info("Worker timeout (module: ~p, event: ~p, protocol: ~p, message: ~p)", [
?MODULE, timeout, udp, DecodedMessage
]),
erldns_events:notify({?MODULE, timeout}),
handle_timeout(WorkerProcessSup, WorkerProcessId);
Error:Reason ->
?set_attributes([{status, <<"error">>}]),
lager:error(
"Worker process crashed (module: ~p, event: ~p, protocol: ~p, error: ~p, reason: ~p, message: ~p)",
[?MODULE, process_crashed, udp, Error, Reason, DecodedMessage]
),
% erldns_events:notify({?MODULE, process_crashed, {udp, Error, Reason, DecodedMessage}}),
{error, {Error, Reason}}
end;
true ->
{error, not_a_question}
end
end
).
-spec handle_timeout(pid(), term()) -> {error, timeout, term()} | {error, timeout}.
handle_timeout(WorkerProcessSup, WorkerProcessId) ->
TerminateResult = supervisor:terminate_child(WorkerProcessSup, WorkerProcessId),
lager:debug("Terminate result: ~p", [TerminateResult]),
case supervisor:restart_child(WorkerProcessSup, WorkerProcessId) of
{ok, NewChild} ->
{error, timeout, NewChild};
{ok, NewChild, _} ->
{error, timeout, NewChild};
{error, Error} ->
erldns_events:notify({?MODULE, restart_failed, {Error}}),
{error, timeout}
end.