Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Move lookup logic out of the handler into the zone cache.

  • Loading branch information...
commit 7e7ccc37fa3202458b1ad9f06a856215bf2b5961 1 parent 3dc72b4
@aeden aeden authored
View
2  ebin/erldns.app
@@ -1,6 +1,6 @@
{application,erldns,
[{description,"Erlang Authoritative DNS Server"},
- {vsn,"2590998"},
+ {vsn,"8dec37c"},
{modules,[erldns,erldns_app,erldns_axfr,erldns_config,
erldns_dnssec,erldns_edns,erldns_encoder,
erldns_handler,erldns_metrics,erldns_packet_cache,
View
2  include/erldns.hrl
@@ -7,7 +7,9 @@
}).
-record(zone, {
+ name,
authority = [],
+ record_count = 0,
records = [],
records_by_name,
records_by_type
View
28 src/erldns_handler.erl
@@ -79,7 +79,7 @@ resolve(Message, AuthorityRecords, Qname, Qtype, Host) ->
% Step 2: Search the available zones for the zone which is the nearest ancestor to QNAME
Zone = erldns_metrics:measure(none, ?MODULE, find_zone, [Qname, lists:last(AuthorityRecords)]),
Records = erldns_metrics:measure(none, ?MODULE, resolve, [Message, Qname, Qtype, Zone, Host, []]),
- RewrittenRecords = erldns_metrics:measure(none, ?MODULE, rewrite_soa_ttl, [Records]),
+ RewrittenRecords = rewrite_soa_ttl(Records),
erldns_metrics:measure(none, ?MODULE, additional_processing, [RewrittenRecords, Host, Zone]).
resolve(Message, Qname, _Qtype, {error, not_authoritative}, _Host, _CnameChain) ->
@@ -87,7 +87,7 @@ resolve(Message, Qname, _Qtype, {error, not_authoritative}, _Host, _CnameChain)
{Authority, Additional} = erldns_records:root_hints(),
Message#dns_message{aa = true, rc = ?DNS_RCODE_NOERROR, authority = Authority, additional = Additional};
resolve(Message, Qname, Qtype, Zone, Host, CnameChain) ->
- lager:debug("Zone has ~p records", [length(Zone#zone.records)]),
+ lager:info("Zone has ~p records", [Zone#zone.record_count]),
% Step 3: Match records
resolve(Message, Qname, Qtype, find_records_by_name(Qname, Zone), Host, CnameChain, Zone).
@@ -135,7 +135,7 @@ resolve_exact_type_match(Message, ?DNS_TYPE_NS, _Host, _CnameChain, MatchedRecor
Message#dns_message{aa = true, rc = ?DNS_RCODE_NOERROR, answers = Message#dns_message.answers ++ MatchedRecords};
resolve_exact_type_match(Message, Qtype, Host, CnameChain, MatchedRecords, Zone, _AuthorityRecords) ->
Answer = lists:last(MatchedRecords),
- NSRecords = delegation_records(Answer#dns_rr.name, Zone#zone.records),
+ NSRecords = delegation_records(Answer#dns_rr.name, Zone),
resolve_exact_type_match(Message, Qtype, Host, CnameChain, MatchedRecords, Zone, _AuthorityRecords, NSRecords).
resolve_exact_type_match(Message, _Qtype, _Host, _CnameChain, MatchedRecords, _Zone, _AuthorityRecords, []) ->
@@ -153,8 +153,7 @@ resolve_exact_type_match(Message, Qtype, Host, CnameChain, _MatchedRecords, Zone
resolve_no_exact_type_match(Message, ?DNS_TYPE_ANY, _Host, _CnameChain, _ExactTypeMatches, _Zone, [], [], AuthorityRecords) ->
Message#dns_message{aa = true, authority = AuthorityRecords};
resolve_no_exact_type_match(Message, _Qtype, _Host, _CnameChain, [], Zone, _MatchedRecords, [], _AuthorityRecords) ->
- Authority = lists:filter(match_type(?DNS_TYPE_SOA), Zone#zone.records),
- Message#dns_message{aa = true, authority = Authority};
+ Message#dns_message{aa = true, authority = Zone#zone.authority};
resolve_no_exact_type_match(Message, _Qtype, _Host, _CnameChain, ExactTypeMatches, _Zone, _MatchedRecords, [], _AuthorityRecords) ->
Message#dns_message{aa = true, answers = Message#dns_message.answers ++ ExactTypeMatches};
resolve_no_exact_type_match(Message, Qtype, _Host, _CnameChain, _ExactTypeMatches, _Zone, MatchedRecords, ReferralRecords, AuthorityRecords) ->
@@ -223,8 +222,7 @@ resolve_best_match_with_wildcard(Message, _Zone, false) ->
Message#dns_message{authority=Authority, additional=Additional};
resolve_best_match_with_wildcard(Message, Zone, true) ->
lager:debug("Qname matched query name"),
- Authority = lists:filter(match_type(?DNS_TYPE_SOA), Zone#zone.records),
- Message#dns_message{rc = ?DNS_RCODE_NXDOMAIN, authority = Authority, aa = true}.
+ Message#dns_message{rc = ?DNS_RCODE_NXDOMAIN, authority = Zone#zone.authority, aa = true}.
% It's not a wildcard CNAME
resolve_best_match_with_wildcard(Message, Qname, Qtype, Host, CnameChain, BestMatchRecords, Zone, []) ->
@@ -241,7 +239,7 @@ resolve_best_match_with_wildcard(Message, Qname, Qtype, Host, CnameChain, BestMa
resolve_best_match_with_wildcard_cname(Message, Qname, Qtype, Host, CnameChain, BestMatchRecords, Zone, CnameRecords).
resolve_best_match_with_wildcard(Message, _Qname, _Qtype, _Host, _CnameChain, _BestMatchRecords, Zone, _CnameRecords, []) ->
- Message#dns_message{aa = true, authority=lists:filter(match_type(?DNS_TYPE_SOA), Zone#zone.records)};
+ Message#dns_message{aa = true, authority=Zone#zone.authority};
resolve_best_match_with_wildcard(Message, _Qname, _Qtype, _Host, _CnameChain, _BestMatchRecords, _Zone, _CnameRecords, TypeMatches) ->
Message#dns_message{aa = true, answers = Message#dns_message.answers ++ TypeMatches}.
@@ -308,7 +306,6 @@ best_match(_Qname, _Labels, _Zone, WildcardMatches) -> WildcardMatches.
%% Various matching functions.
match_type(Type) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.type =:= Type end.
match_wildcard() -> fun(R) when is_record(R, dns_rr) -> lists:any(fun(L) -> L =:= <<"*">> end, dns:dname_to_labels(R#dns_rr.name)) end.
-match_glue(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.data =:= #dns_rrdata_ns{dname=Name} end.
%% Replacement functions.
replace_name(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr{name = Name} end.
@@ -316,10 +313,7 @@ replace_name(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr{name = Name}
%% Find all delegation records for the given Name in the provided
%% Records. This function may return an empty list, which means
%% the record is not a glue record.
-delegation_records(Name, Records) -> lists:filter(fun(R) -> apply(match_type(?DNS_TYPE_NS), [R]) and apply(match_glue(Name), [R]) end, Records).
-
-normalize_name(Name) when is_list(Name) -> string:to_lower(Name);
-normalize_name(Name) when is_binary(Name) -> list_to_binary(string:to_lower(binary_to_list(Name))).
+delegation_records(Name, _Zone) -> erldns_zone_cache:get_delegations(Name).
%% See if additional processing is necessary.
additional_processing(Message, _Host, {error, _}) ->
@@ -364,17 +358,13 @@ get_authority(MessageOrName) ->
{error, _} -> []
end.
-in_zone(Name, Zone) -> erldns_zone_cache:in_zone(Name, Zone).
+in_zone(Name, _Zone) -> erldns_zone_cache:in_zone(Name).
%% Find the zone for the given name.
find_zone(Qname) -> erldns_zone_cache:find_zone(Qname).
find_zone(Qname, Authority) -> erldns_zone_cache:find_zone(Qname, Authority).
-find_records_by_name(Name, Zone) ->
- case dict:find(normalize_name(Name), Zone#zone.records_by_name) of
- {ok, RecordSet} -> RecordSet;
- error -> []
- end.
+find_records_by_name(Name, _Zone) -> erldns_zone_cache:get_records_by_name(Name).
%% Update the message counts and set the QR flag to true.
complete_response(Message) ->
View
3  src/erldns_metrics.erl
@@ -31,9 +31,8 @@ display() ->
slowest() ->
gen_server:cast(?SERVER, {display, slowest}).
-measure(Name, Module, FunctionName, Args) when is_list(Args) ->
+measure(_, Module, FunctionName, Args) when is_list(Args) ->
{T, R} = timer:tc(Module, FunctionName, Args),
- erldns_metrics:insert(Name, T),
lager:info("~p:~p took ~p ms", [Module, FunctionName, T / 1000]),
R;
measure(Name, Module, FunctionName, Arg) -> measure(Name, Module, FunctionName, [Arg]).
View
140 src/erldns_zone_cache.erl
@@ -6,8 +6,8 @@
-include("erldns.hrl").
% API
--export([start_link/0, get/1, put/2, get_authority/1, put_authority/2]).
--export([in_zone/2, find_authority/1, find_zone/1, find_zone/2]).
+-export([start_link/0, get_zone/1, put_zone/2, get_authority/1, put_authority/2, get_delegations/1, get_records_by_name/1, in_zone/1]).
+-export([find_authority/1, find_zone/1, find_zone/2]).
% Internal API
-export([build_named_index/1]).
@@ -23,16 +23,18 @@
-define(SERVER, ?MODULE).
--record(state, {zones}).
+-record(state, {zones, authorities}).
%% Public API
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-get(Name) ->
+get_zone(Name) ->
gen_server:call(?SERVER, {get, Name}).
-put(Name, Zone) ->
+
+put_zone(Name, Zone) ->
gen_server:call(?SERVER, {put, Name, Zone}).
+
get_authority(Message) when is_record(Message, dns_message) ->
case Message#dns_message.questions of
[] -> [];
@@ -42,63 +44,108 @@ get_authority(Message) when is_record(Message, dns_message) ->
end;
get_authority(Name) ->
gen_server:call(?SERVER, {get_authority, Name}).
+
put_authority(Name, Authority) ->
gen_server:call(?SERVER, {put_authority, Name, Authority}).
-in_zone(Name, Zone) ->
- case dict:is_key(Name, Zone#zone.records_by_name) of
- true -> true;
- false ->
- case dns:dname_to_labels(Name) of
- [] -> false;
- [_] -> false;
- [_|Labels] -> in_zone(dns:labels_to_dname(Labels), Zone)
- end
+get_delegations(Name) ->
+ Result = gen_server:call(?SERVER, {get_delegations, Name}),
+ lager:info("get_delegations(~p): ~p", [Name, Result]),
+ case Result of
+ {ok, Delegations} -> Delegations;
+ _ -> []
end.
+get_records_by_name(Name) ->
+ gen_server:call(?SERVER, {get_records_by_name, Name}).
+
+in_zone(Name) ->
+ gen_server:call(?SERVER, {in_zone, Name}).
+
%% Gen server hooks
init([]) ->
- ets:new(zone_cache, [set, named_table]),
- ets:new(authority_cache, [set, named_table]),
- {ok, #state{}}.
+ Zones = dict:new(),
+ Authorities = dict:new(),
+ {ok, #state{zones = Zones, authorities = Authorities}}.
handle_call({get, Name}, _From, State) ->
- lager:info("handle_call({get, ~p})", [Name]),
- case erldns_metrics:measure(none, ets, lookup, [zone_cache, normalize_name(Name)]) of
- [{Name, {Zone}}] -> {reply, {ok, Zone}, State};
+ case dict:find(normalize_name(Name), State#state.zones) of
+ {ok, Zone} -> {reply, {ok, Zone#zone{name = normalize_name(Name), records = [], records_by_name=trimmed}}, State};
_ -> {reply, {error, zone_not_found}, State}
end;
+
+handle_call({get_delegations, Name}, _From, State) ->
+ case find_zone_in_cache(Name, State) of
+ {ok, Zone} ->
+ Records = lists:filter(fun(R) -> apply(match_type(?DNS_TYPE_NS), [R]) and apply(match_glue(Name), [R]) end, Zone#zone.records),
+ {reply, {ok, Records}, State};
+ Response ->
+ lager:info("Failed to get zone for ~p: ~p", [Name, Response]),
+ {reply, Response, State}
+ end;
+
handle_call({put, Name, Zone}, _From, State) ->
- ets:insert(zone_cache, {Name, {Zone}}),
- {reply, ok, State};
+ Zones = dict:store(normalize_name(Name), Zone, State#state.zones),
+ {reply, ok, State#state{zones = Zones}};
+
handle_call({get_authority, Name}, _From, State) ->
- case ets:lookup(authority_cache, normalize_name(Name)) of
- [{Name, {Authority}}] ->
- {reply, {ok, Authority}, State};
+ case dict:find(normalize_name(Name), State#state.authorities) of
+ {ok, Authority} -> {reply, {ok, Authority}, State};
_ ->
case load_authority(Name) of
[] -> {reply, {error, authority_not_found}, State};
- Authority -> {reply, {ok, Authority}, State}
+ Authority ->
+ Authorities = dict:store(normalize_name(Name), Authority, State#state.authorities),
+ {reply, {ok, Authority}, State#state{authorities = Authorities}}
end
end;
+
handle_call({put_authority, Name, Authority}, _From, State) ->
- lager:info("handle_call({put, ~p})", [Name]),
- ets:insert(authority_cache, {Name, {Authority}}),
- {reply, ok, State}.
+ Authorities = dict:store(normalize_name(Name), Authority, State#state.authorities),
+ {reply, ok, State#state{authorities = Authorities}};
+
+handle_call({get_records_by_name, Name}, _From, State) ->
+ case find_zone_in_cache(Name, State) of
+ {ok, Zone} ->
+ case dict:find(normalize_name(Name), Zone#zone.records_by_name) of
+ {ok, RecordSet} -> {reply, RecordSet, State};
+ _ -> {reply, [], State}
+ end;
+ Response ->
+ lager:info("Failed to get zone: ~p", [Response]),
+ {reply, [], State}
+ end;
+
+handle_call({in_zone, Name}, _From, State) ->
+ case find_zone_in_cache(Name, State) of
+ {ok, Zone} ->
+ {reply, internal_in_zone(Name, Zone), State};
+ _ ->
+ {reply, false, State}
+ end.
handle_cast(_Message, State) ->
{noreply, State}.
handle_info(_Message, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
- ets:delete(zone_cache),
- ets:delete(authority_cache),
ok.
code_change(_PreviousVersion, State, _Extra) ->
{ok, State}.
% Internal API%
+internal_in_zone(Name, Zone) ->
+ case dict:is_key(normalize_name(Name), Zone#zone.records_by_name) of
+ true -> true;
+ false ->
+ case dns:dname_to_labels(Name) of
+ [] -> false;
+ [_] -> false;
+ [_|Labels] -> internal_in_zone(dns:labels_to_dname(Labels), Zone)
+ end
+ end.
+
%% Get the SOA authority for the current query.
find_authority(Qname) ->
case dns:dname_to_labels(Qname) of
@@ -116,10 +163,8 @@ load_authority(Qname) ->
load_authority(Qname, [F([normalize_name(Qname)]) || F <- soa_functions()]).
load_authority(_Qname, []) -> [];
-load_authority(Qname, Authorities) ->
- Authority = lists:last(Authorities),
- ets:insert(authority_cache, {normalize_name(Qname), {Authority}}),
- Authority.
+load_authority(_Qname, Authorities) ->
+ lists:last(Authorities).
% Find the zone for the given name.
find_zone(Qname) ->
@@ -135,6 +180,7 @@ find_zone(Qname, Authorities) when is_list(Authorities) ->
lager:info("Finding zone ~p (Authorities: ~p)", [Qname, Authorities]),
Authority = lists:last(Authorities),
find_zone(Qname, Authority);
+
find_zone(Qname, Authority) when is_record(Authority, dns_rr) ->
lager:info("Finding zone ~p (Authority: ~p)", [Qname, Authority]),
Name = normalize_name(Qname),
@@ -142,7 +188,7 @@ find_zone(Qname, Authority) when is_record(Authority, dns_rr) ->
[] -> {error, zone_not_found};
[_] -> {error, zone_not_found};
[_|Labels] ->
- case erldns_metrics:measure(none, erldns_zone_cache, get, [Name]) of
+ case erldns_metrics:measure(none, erldns_zone_cache, get_zone, [Name]) of
{ok, Zone} -> Zone;
{error, zone_not_found} ->
case Name =:= Authority#dns_rr.name of
@@ -152,13 +198,26 @@ find_zone(Qname, Authority) when is_record(Authority, dns_rr) ->
end
end.
+find_zone_in_cache(Qname, State) ->
+ Name = normalize_name(Qname),
+ case dns:dname_to_labels(Name) of
+ [] -> {error, zone_not_found};
+ [_] -> {error, zone_not_found};
+ [_|Labels] ->
+ case dict:find(Name, State#state.zones) of
+ {ok, Zone} -> {ok, Zone};
+ error -> find_zone_in_cache(dns:labels_to_dname(Labels), State)
+ end
+ end.
+
make_zone(Qname) ->
lager:info("Constructing new zone for ~p", [Qname]),
DbRecords = erldns_metrics:measure(Qname, erldns_pgsql, lookup_records, [normalize_name(Qname)]),
Records = lists:usort(lists:flatten(lists:map(fun(R) -> erldns_pgsql_responder:db_to_record(Qname, R) end, DbRecords))),
RecordsByName = erldns_metrics:measure(Qname, ?MODULE, build_named_index, [Records]),
- Zone = #zone{records = Records, records_by_name = RecordsByName},
- erldns_zone_cache:put(Qname, Zone),
+ Authorities = lists:filter(match_type(?DNS_TYPE_SOA), Records),
+ Zone = #zone{record_count = length(Records), authority = Authorities, records = Records, records_by_name = RecordsByName},
+ erldns_zone_cache:put_zone(Qname, Zone),
Zone.
build_named_index(Records) -> build_named_index(Records, dict:new()).
@@ -177,3 +236,8 @@ normalize_name(Name) when is_binary(Name) -> list_to_binary(string:to_lower(bina
%% registered responders.
soa_functions() ->
lists:map(fun(M) -> fun M:get_soa/1 end, erldns_handler:get_responder_modules()).
+
+%% Various matching functions.
+match_type(Type) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.type =:= Type end.
+%match_wildcard() -> fun(R) when is_record(R, dns_rr) -> lists:any(fun(L) -> L =:= <<"*">> end, dns:dname_to_labels(R#dns_rr.name)) end.
+match_glue(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.data =:= #dns_rrdata_ns{dname=Name} end.
Please sign in to comment.
Something went wrong with that request. Please try again.