Packages
erldns
10.4.4
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]`
Emitted when the query throttle pipe stops the request (e.g. UDP client over limit);
response is truncated.
- **Measurements:** `#{count => non_neg_integer()}` — request count that triggered throttle
- **Metadata:** `#{transport => udp, host => host()}`
""".
-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.