Packages
erldns
9.0.0
11.1.0
11.0.3
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/pipes/erldns_query_throttle.erl
-module(erldns_query_throttle).
-moduledoc """
Stateful query throttling. Currently only throttles `ANY` queries.
We should throttle `ANY` and `RRSIG` queries to discourage use of our authoritative name servers
for reflection/amplification attacks.
## Configuration
```erlang
{erldns, [
{query_throttle, #{
enabled := boolean(), %% defaults to true
limit := non_neg_integer(), %% Number of queries to allow, defaults to 1
ttl := non_neg_integer(), %% Seconds, defaults to 30
}}
]}
```
## Telemetry events
- `[erldns, pipeline, throttle]` spans with `host` in the metadata
as triggered by `m:segmented_cache`.
- `[erldns, pipeline, throttle]` with `host` in the metadata.
""".
-include_lib("dns_erlang/include/dns.hrl").
-behaviour(erldns_pipeline).
-export([prepare/1, call/2]).
-export([start_link/0, clear/0, merger/2]).
-type host() :: inet:ip_address() | inet:hostname().
-define(DEFAULT_LIMIT, 1).
-define(DEFAULT_BUCKETS, 2).
-define(DEFAULT_CACHE_TTL, 60).
-doc "`c:erldns_pipeline:prepare/1` callback.".
-spec prepare(erldns_pipeline:opts()) -> disabled | erldns_pipeline:opts().
prepare(Opts) ->
case enabled() of
false -> disabled;
true -> Opts#{packet_throttle_limit => default_limit()}
end.
-doc "`c:erldns_pipeline:call/2` callback.".
-spec call(dns:message(), erldns_pipeline:opts()) -> erldns_pipeline:return().
call(Msg, #{transport := udp, host := Host, packet_throttle_limit := Limit}) ->
case should_throttle(Msg, Host, Limit) of
{true, ReqCount} ->
Metadata = #{transport => udp, host => Host},
telemetry:execute([erldns, pipeline, throttle], #{count => ReqCount}, Metadata),
{stop, Msg#dns_message{
tc = true,
aa = true,
rc = ?DNS_RCODE_NOERROR
}};
false ->
Msg
end;
call(Msg, _) ->
Msg.
-doc false.
-spec start_link() -> ignore | gen_server:start_ret().
start_link() ->
case enabled() of
false ->
ignore;
true ->
Config = #{
prefix => [erldns, pipeline, throttle],
scope => erldns,
strategy => lru,
merger_fun => fun ?MODULE:merger/2,
segment_num => ?DEFAULT_BUCKETS,
ttl => {seconds, default_ttl() div ?DEFAULT_BUCKETS}
},
segmented_cache:start_link(?MODULE, Config)
end.
-spec should_throttle(dns:message(), host(), non_neg_integer()) ->
false | {true, non_neg_integer()}.
should_throttle(#dns_message{questions = Questions}, Host, Limit) ->
HasAny = lists:any(
fun
(#dns_query{type = ?DNS_TYPE_ANY}) -> true;
(#dns_query{type = ?DNS_TYPE_RRSIG}) -> true;
(#dns_query{type = _}) -> false
end,
Questions
),
HasAny andalso should_throttle(Host, Limit).
-spec should_throttle(host(), non_neg_integer()) -> false | {true, non_neg_integer()}.
should_throttle({127, 0, 0, 1}, _) ->
false;
should_throttle({0, 0, 0, 0, 0, 0, 0, 1}, _) ->
false;
should_throttle(Host, Limit) ->
case segmented_cache:get_entry(?MODULE, Host) of
not_found ->
segmented_cache:put_entry(?MODULE, Host, 1),
false;
ReqCount when is_integer(ReqCount), ReqCount < Limit ->
segmented_cache:put_entry(?MODULE, Host, ReqCount),
false;
ReqCount when is_integer(ReqCount), ReqCount >= Limit ->
segmented_cache:put_entry(?MODULE, Host, ReqCount),
{true, ReqCount}
end.
-doc "Clear the cache".
-spec clear() -> true.
clear() ->
segmented_cache:delete_pattern(?MODULE, '_').
-doc false.
-spec merger(non_neg_integer(), non_neg_integer()) -> non_neg_integer().
merger(_A, B) ->
B + 1.
-spec enabled() -> boolean().
enabled() ->
case application:get_env(erldns, query_throttle, #{}) of
#{enabled := Value} when is_boolean(Value) ->
Value;
_ ->
true
end.
-spec default_limit() -> pos_integer().
default_limit() ->
case application:get_env(erldns, query_throttle, #{}) of
#{limit := Value} when is_integer(Value), 0 < Value ->
Value;
_ ->
?DEFAULT_LIMIT
end.
-spec default_ttl() -> pos_integer().
default_ttl() ->
case application:get_env(erldns, query_throttle, #{}) of
#{ttl := Value} when is_integer(Value), 0 < Value ->
Value;
_ ->
?DEFAULT_CACHE_TTL
end.