From 28bc4ae4d9cf9f34f9dcd99da1c89bdb56a2bd38 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 6 Apr 2016 10:07:48 +0200 Subject: Add unit test for validation, from dnssecport:handle_call(). - The port now returns the RRset (DS, chain, trust root and all RRSIG's). This in preparatino for when this data will be normalised. - dnssecport decodes and encodes DNS data. - v1 stores the DS RR in the leaf and the rest, including the DS RRSIG, in the chain. --- src/dns.erl | 69 ++++++++++++++++++++++++++++++++ src/dnssecport.erl | 113 +++++++++++++++++++++++++++++++++++------------------ src/v1.erl | 17 ++++---- 3 files changed, 153 insertions(+), 46 deletions(-) create mode 100644 src/dns.erl (limited to 'src') diff --git a/src/dns.erl b/src/dns.erl new file mode 100644 index 0000000..24fb8fb --- /dev/null +++ b/src/dns.erl @@ -0,0 +1,69 @@ +%%% Copyright (c) 2016, NORDUnet A/S. +%%% See LICENSE for licensing information. + +-module(dns). +-export([split_rrset/1, encode_rr/1, encode_rrset/1]). + +decode_name_label(Name) -> + <> = Name, + {Label, Rest}. + +encode_name_label(Label) -> + Len = byte_size(Label), + <>. + +decode_name(RR) -> + decode_name(RR, []). +decode_name(<<0, Rest/binary>>, Acc) -> + {lists:reverse(Acc), Rest}; +decode_name(Name, Acc) -> + {Label, Rest} = decode_name_label(Name), + decode_name(Rest, [Label | Acc]). + +-spec encode_name(list()) -> binary(). +encode_name(Name) -> + encode_name(Name, []). +encode_name([], Acc) -> + Bin = list_to_binary(lists:reverse(Acc)), + <>; +encode_name([H|T], Acc) -> + encode_name(T, [encode_name_label(H) | Acc]). + +-spec decode_rr(binary()) -> {list(), binary()}. +decode_rr(RR) -> + {Name, RestRR} = decode_name(RR), + <> = RestRR, + {[Name, Type, Class, TTL, RDATA], Rest}. + +-spec split_rrset(binary()) -> list(). +split_rrset(RRSet) -> + split_rrset(RRSet, []). +split_rrset(<<>>, Acc) -> + lists:reverse(Acc); +split_rrset(RRSet, Acc) -> + {RR, Rest} = decode_rr(RRSet), + split_rrset(Rest, [RR | Acc]). + +-spec encode_rr(list()) -> binary(). +encode_rr([Name, Type, Class, TTL, RDATA]) -> + EncodedName = encode_name(Name), + RDLength = byte_size(RDATA), + <>. + +-spec encode_rrset(list()) -> binary(). +encode_rrset(RRSet) -> + encode_rrset(RRSet, []). +encode_rrset([], Acc) -> + list_to_binary(lists:reverse(Acc)); +encode_rrset([H|T], Acc) -> + encode_rrset(T, [encode_rr(H) | Acc]). diff --git a/src/dnssecport.erl b/src/dnssecport.erl index 804e727..02f919a 100644 --- a/src/dnssecport.erl +++ b/src/dnssecport.erl @@ -4,7 +4,7 @@ -module(dnssecport). -behaviour(gen_server). -export([start_link/0, stop/0]). --export([validate/2]). +-export([validate/1]). %% gen_server callbacks. -export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2, code_change/3]). @@ -18,36 +18,40 @@ start_link() -> stop() -> gen_server:call(?MODULE, stop). -validate(ValidateRR, SupportRRs) -> - gen_server:call(?MODULE, {validate, [ValidateRR, SupportRRs]}). +validate(Data) -> + gen_server:call(?MODULE, {validate, Data}). -record(state, {port :: port()}). init(Program) -> lager:debug("starting dnssec service"), - Port = create_port(Program, []), % TODO: Pass path to dir with trust root. + Port = create_port(Program, []), % TODO: Pass path to trust root file. {ok, #state{port = Port}}. +decode_response(Response) -> + <> = Response, + {ok, Status, dns:split_rrset(RRSet)}. + handle_call(stop, _From, State) -> lager:debug("dnssec stop request received"), stop_port(State); -handle_call({validate, [ValidateRR, SupportRRs]}, _From, State) -> - lager:debug("dnssec validate incoming request: ~p", [ValidateRR]), +handle_call({validate, Data}, _From, State) -> case State#state.port of undefined -> {error, noport}; Port when is_port(Port) -> - S = list_to_binary(SupportRRs), - Port ! {self(), {command, <>}}, + Port ! {self(), {command, dns:encode_rrset(Data)}}, receive {Port, {data, Response}} -> - lager:debug("received response from dnssec port: ~p", - [Response]), - case Response of - "valid" -> - {reply, ok, State}; - Err -> - {reply, {error, Err}, State} + case decode_response(list_to_binary(Response)) of + {ok, 400, [DS | Chain]} -> + {reply, + {ok, [dns:encode_rr(DS) | dns:encode_rrset(Chain)]}, + State}; + {ok, Error, _} -> + {reply, {error, Error}, State}; + {error, Reason} -> + {stop, {protocolerror, Reason}, State} end; {Port, {exit_status, ExitStatus}} -> lager:error("dnssec port ~p exiting with status ~p", @@ -97,34 +101,67 @@ stop_port(State) -> %%%%%%%%%%%%%%%%%%%% %% Unit tests. -start_test_port(TestType) -> - Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]), - ?debugFmt("Port: ~p", [Port]), - Port. +-define(TA_FILE, "test/testdata/dnssec/trust_anchors"). +-define(REQ1_FILE, "test/testdata/dnssec/req.1"). + +start_test_port() -> + create_port("priv/dnssecport", [?TA_FILE]). + stop_test_port(Port) -> {stop, closed, _State} = stop_port(#state{port = Port}), ok. -err_test_() -> - {setup, - fun() -> start_test_port(err) end, - fun(Port) -> stop_test_port(Port) end, - fun(Port) -> - R = handle_call({validate, [<<"invalid-DS">>, []]}, - self(), #state{port = Port}), - [ - ?_assertMatch({reply, {error, "err"}, _State}, R) - ] - end}. +read_submission_from_file(Filename) -> + {ok, Data} = file:read_file(Filename), + dns:split_rrset(Data). + +read_dec_enc_test_() -> + DecodedRRset = read_submission_from_file(?REQ1_FILE), + {ok, FileContent} = file:read_file(?REQ1_FILE), + [?_assertEqual(FileContent, dns:encode_rrset(DecodedRRset))]. -ok_test_() -> +full_test_() -> {setup, - fun() -> start_test_port(ok) end, - fun(Port) -> stop_test_port(Port) end, - fun(Port) -> - R = handle_call({validate, [<<"invalid-DS">>, []]}, + fun() -> + start_test_port() end, + fun(Port) -> + stop_test_port(Port) end, + fun(Port) -> + R = handle_call({validate, read_submission_from_file(?REQ1_FILE)}, self(), #state{port = Port}), [ - ?_assertMatch({reply, ok, _State}, R) - ] - end}. + ?_assertMatch({reply, {ok, _}, _State}, R) + ] end + }. + +%% start_test_port(TestType) -> +%% Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]), +%% ?debugFmt("Port: ~p", [Port]), +%% Port. +%% stop_test_port(Port) -> +%% {stop, closed, _State} = stop_port(#state{port = Port}), +%% ok. + +%% err_test_() -> +%% {setup, +%% fun() -> start_test_port(err) end, +%% fun(Port) -> stop_test_port(Port) end, +%% fun(Port) -> +%% R = handle_call({validate, [<<"invalid-DS">>, []]}, +%% self(), #state{port = Port}), +%% [ +%% ?_assertMatch({reply, {error, "err"}, _State}, R) +%% ] +%% end}. + +%% ok_test_() -> +%% {setup, +%% fun() -> start_test_port(ok) end, +%% fun(Port) -> stop_test_port(Port) end, +%% fun(Port) -> +%% R = handle_call({validate, [<<"invalid-DS">>, []]}, +%% self(), #state{port = Port}), +%% [ +%% ?_assertMatch({reply, ok, _State}, R) +%% ] +%% end}. diff --git a/src/v1.erl b/src/v1.erl index 86cd799..bab77aa 100644 --- a/src/v1.erl +++ b/src/v1.erl @@ -156,8 +156,8 @@ add_ds(Input) -> case decode_chain(List) of {invalid, ErrText} -> err400(io:format("add-ds-rr: ~p", [ErrText]), List); - [DSRR, DSRRSIG | SupportRRs] -> - add_ds_helper(DSRR, DSRRSIG, SupportRRs); + Data when is_list(Data) -> + add_ds_helper(Data); _ -> err400("add-ds-rr: missing one or more entries", List) end; @@ -173,10 +173,11 @@ decode_chain(List) -> L end. -add_ds_helper(DSRR, DSRRSIG, Support) -> - case dnssecport:dnssec_validate([DSRR, DSRRSIG], Support) of - ok -> - success(catlfish:add_chain(DSRR, [DSRRSIG | Support], normal)); - _ -> - err400("add-ds-rr: invalid DS record", [DSRR, DSRRSIG | Support]) +add_ds_helper(Data) -> + case dnssecport:dnssec_validate(Data) of + {ok, [DS | Chain]} -> + success(catlfish:add_chain(DS, Chain, normal)); + {error, ErrorCode} -> + err400(io:format("add-ds-rr: invalid DS record: ~p", [ErrorCode]), + Data) end. -- cgit v1.1