-ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -include("rebar_version.hrl"). %%%=================================================================== %%% Message body functions %%%=================================================================== message_empty_test() -> Msg = #dns_message{}, Bin = encode_message(Msg), ?assertEqual(Msg, decode_message(Bin)). message_query_test() -> Qs = [#dns_query{name = <<"example">>, type = ?DNS_TYPE_A}], QLen = length(Qs), Msg = #dns_message{qc = QLen, questions = Qs}, Bin = encode_message(Msg), ?assertEqual(Msg, decode_message(Bin)). message_other_test() -> QName = <<"i .txt.example.org">>, Qs = [#dns_query{name = QName, type = ?DNS_TYPE_TXT}], As = [ #dns_rr{ name = QName, type = ?DNS_TYPE_TXT, ttl = 0, data = #dns_rrdata_txt{txt = [QName]} } ], QLen = length(Qs), ALen = length(As), Msg = #dns_message{qc = QLen, anc = ALen, questions = Qs, answers = As}, Bin = encode_message(Msg), ?assertEqual(Msg, decode_message(Bin)). message_edns_test() -> QName = <<"_http._tcp.example.org">>, Qs = [#dns_query{name = QName, type = ?DNS_TYPE_PTR}], Ans = [ #dns_rr{ name = QName, type = ?DNS_TYPE_PTR, ttl = 42, data = #dns_rrdata_ptr{ dname = <<"Example\ Site._http._tcp.example.org">> } } ], LLQ = #dns_opt_llq{ opcode = ?DNS_LLQOPCODE_SETUP, errorcode = ?DNS_LLQERRCODE_NOERROR, id = 42, leaselife = 7200 }, ECS = #dns_opt_ecs{ family = 1, source_prefix_length = 24, scope_prefix_length = 0, address = <<1, 1, 1>> }, Ads = [ #dns_optrr{ udp_payload_size = 4096, ext_rcode = 0, version = 0, dnssec = false, data = [LLQ, ECS] } ], QLen = length(Qs), AnsLen = length(Ans), AdsLen = length(Ads), Msg = #dns_message{ qc = QLen, anc = AnsLen, adc = AdsLen, questions = Qs, answers = Ans, additional = Ads }, Bin = encode_message(Msg), ?assertEqual(Msg, decode_message(Bin)). tsig_no_tsig_test() -> MsgBin = encode_message(#dns_message{}), Name = <<"name">>, Value = <<"value">>, ?assertException(throw, no_tsig, verify_tsig(MsgBin, Name, Value)). tsig_bad_key_test() -> MsgId = random_id(), B64 = <<"abcdefgh">>, TSIGData = #dns_rrdata_tsig{ alg = ?DNS_TSIG_ALG_MD5, time = unix_time(), fudge = 0, mac = B64, msgid = MsgId, err = 0, other = <<>> }, TSIG = #dns_rr{ name = <<"name_a">>, type = ?DNS_TYPE_TSIG, ttl = 0, data = TSIGData }, Msg = #dns_message{id = MsgId, adc = 1, additional = [TSIG]}, MsgBin = encode_message(Msg), Result = verify_tsig(MsgBin, <<"name_b">>, B64), ?assertEqual({error, ?DNS_TSIGERR_BADKEY}, Result). tsig_bad_alg_test() -> Id = random_id(), Name = <<"keyname">>, Data = #dns_rrdata_tsig{ alg = <<"null">>, time = unix_time(), fudge = 0, mac = <<"MAC">>, msgid = Id, err = 0, other = <<>> }, RR = #dns_rr{name = Name, type = ?DNS_TYPE_TSIG, ttl = 0, data = Data}, Msg = #dns_message{id = Id, adc = 1, additional = [RR]}, MsgBin = encode_message(Msg), Result = verify_tsig(MsgBin, Name, <<"secret">>), ?assertEqual({error, ?DNS_TSIGERR_BADKEY}, Result). tsig_bad_sig_test() -> application:start(crypto), MsgId = random_id(), Name = <<"keyname">>, Value = crypto:strong_rand_bytes(20), Msg = #dns_message{id = MsgId}, SignedMsg = add_tsig(Msg, ?DNS_TSIG_ALG_MD5, Name, Value, 0), [#dns_rr{data = TSIGData} = TSIG] = SignedMsg#dns_message.additional, BadTSIG = TSIG#dns_rr{data = TSIGData#dns_rrdata_tsig{mac = Value}}, BadSignedMsg = Msg#dns_message{adc = 1, additional = [BadTSIG]}, BadSignedMsgBin = encode_message(BadSignedMsg), Result = verify_tsig(BadSignedMsgBin, Name, Value), ?assertEqual({error, ?DNS_TSIGERR_BADSIG}, Result). tsig_badtime_test_() -> application:start(crypto), MsgId = random_id(), Name = <<"keyname">>, Secret = crypto:strong_rand_bytes(20), Msg = #dns_message{id = MsgId}, Fudge = 30, SignedMsg = add_tsig(Msg, ?DNS_TSIG_ALG_MD5, Name, Secret, 0), SignedMsgBin = encode_message(SignedMsg), Now = unix_time(), [ ?_test( begin BadNow = Now + (Throwoff * Fudge), Options = [{fudge, 30}, {time, BadNow}], Result = verify_tsig(SignedMsgBin, Name, Secret, Options), ?assertEqual({error, ?DNS_TSIGERR_BADTIME}, Result) end ) || Throwoff <- [-2, 2] ]. tsig_ok_test_() -> application:start(crypto), MsgId = random_id(), Name = <<"keyname">>, Secret = crypto:strong_rand_bytes(20), Algs = [ ?DNS_TSIG_ALG_MD5, ?DNS_TSIG_ALG_SHA1, ?DNS_TSIG_ALG_SHA224, ?DNS_TSIG_ALG_SHA256, ?DNS_TSIG_ALG_SHA384, ?DNS_TSIG_ALG_SHA512 ], Msg = #dns_message{id = MsgId}, Options = [{time, unix_time()}, {fudge, 30}], [ {Alg, ?_test( begin SignedMsg = add_tsig(Msg, Alg, Name, Secret, 0), SignedMsgBin = encode_message(SignedMsg), Result = case verify_tsig(SignedMsgBin, Name, Secret, Options) of {ok, _MAC} -> ok; Error -> Error end, ?assertEqual(ok, Result) end )} || Alg <- Algs ]. tsig_wire_test_() -> application:start(crypto), Now = 1292459455, Keyname = <<"key.name">>, Secret = base64:decode(<<"8F1BRL+xp3gNW1GfbSnlUuvUtxQ=">>), {ok, Cases} = file:consult(filename:join(prefix(), "tsig_wire_samples.txt")), [ {Alg, ?_test( begin Result = case verify_tsig(Msg, Keyname, Secret, [{time, Now}]) of {ok, MAC} when is_binary(MAC) -> ok; X -> X end, ?assertEqual(ok, Result) end )} || {Alg, Msg} <- Cases ]. %%%=================================================================== %%% Record data functions %%%=================================================================== decode_encode_rrdata_wire_samples_test_() -> {ok, Cases} = file:consult(filename:join(prefix(), "rrdata_wire_samples.txt")), ToTestName = fun({Class, Type, Bin}) -> Fmt = "~p/~p/~n~p", Args = [Class, Type, Bin], lists:flatten(io_lib:format(Fmt, Args)) end, [ { ToTestName(Case), ?_test( begin NewBin = case decode_rrdata(Class, Type, TestBin, TestBin) of TestBin when Type =:= 999 -> TestBin; TestBin -> throw(not_decoded); Record -> {Bin, _} = encode_rrdata( 0, Class, Record, gb_trees:empty() ), Bin end, ?assertEqual(TestBin, NewBin) end ) } || {Class, Type, TestBin} = Case <- Cases ]. decode_encode_rrdata_test_() -> %% For testing records that don't have wire samples Cases = [ {?DNS_TYPE_MB, #dns_rrdata_mb{madname = <<"example.com">>}}, {?DNS_TYPE_MG, #dns_rrdata_mg{madname = <<"example.com">>}}, {?DNS_TYPE_MINFO, #dns_rrdata_minfo{ rmailbx = <<"a.b">>, emailbx = <<"c.d">> }}, {?DNS_TYPE_MR, #dns_rrdata_mr{newname = <<"example.com">>}}, {?DNS_TYPE_CAA, #dns_rrdata_caa{ flags = 0, tag = <<"issue">>, value = <<"letsencrypt.org">> }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{?DNS_SVCB_PARAM_PORT_NUMBER => 8080} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{?DNS_SVCB_PARAM_NO_DEFAULT_ALPN => none} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{?DNS_SVCB_PARAM_ALPN => <<"h2,h3">>} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{?DNS_SVCB_PARAM_ECHCONFIG => <<"123abc">>} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{?DNS_SVCB_PARAM_IPV4HINT => <<"1.2.3.4,1.2.3.5">>} }}, {?DNS_TYPE_SVCB, #dns_rrdata_svcb{ svc_priority = 0, target_name = <<"target.example.com">>, svc_params = #{ ?DNS_SVCB_PARAM_IPV6HINT => <<"2001:0db8:85a3:0000:0000:8a2e:0370:7334,2001:0db8:85a3:0000:0000:8a2e:0370:7335">> } }} ], [ ?_test( begin {Encoded, _NewCompMap} = encode_rrdata( 0, ?DNS_CLASS_IN, Data, gb_trees:empty() ), Decoded = decode_rrdata(?DNS_CLASS_IN, Type, Encoded, Encoded), ?assertEqual(Data, Decoded) end ) || {Type, Data} <- Cases ]. %%%=================================================================== %%% EDNS data functions %%%=================================================================== decode_encode_optdata_test_() -> Cases = [ #dns_opt_llq{ opcode = ?DNS_LLQOPCODE_SETUP, errorcode = ?DNS_LLQERRCODE_NOERROR, id = 123, leaselife = 456 }, #dns_opt_ul{lease = 789}, #dns_opt_nsid{data = <<"hi">>}, #dns_opt_ecs{ family = 1, source_prefix_length = 24, scope_prefix_length = 0, address = <<1, 1, 1>> }, #dns_opt_unknown{id = 999, bin = <<"hi">>} ], [ ?_assertEqual([Case], decode_optrrdata(encode_optrrdata([Case]))) || Case <- Cases ]. decode_encode_optdata_owner_test_() -> application:start(crypto), Cases = [ #dns_opt_owner{ seq = rand:uniform(255), primary_mac = crypto:strong_rand_bytes(6), wakeup_mac = crypto:strong_rand_bytes(6), password = crypto:strong_rand_bytes(6) }, #dns_opt_owner{ seq = rand:uniform(255), primary_mac = crypto:strong_rand_bytes(6), wakeup_mac = crypto:strong_rand_bytes(6), password = crypto:strong_rand_bytes(4) }, #dns_opt_owner{ seq = rand:uniform(255), primary_mac = crypto:strong_rand_bytes(6), wakeup_mac = crypto:strong_rand_bytes(6), _ = <<>> }, #dns_opt_owner{ seq = rand:uniform(255), primary_mac = crypto:strong_rand_bytes(6), _ = <<>> } ], [ ?_assertEqual([Case], decode_optrrdata(encode_optrrdata([Case]))) || Case <- Cases ]. decode_encode_svcb_params_test() -> Cases = [ {#{}, #{}}, {#{?DNS_SVCB_PARAM_PORT => 8079}, #{?DNS_SVCB_PARAM_PORT => 8079}}, {#{port => 8080}, #{?DNS_SVCB_PARAM_PORT => 8080}} ], [ ?assertEqual(Expected, decode_svcb_svc_params(encode_svcb_svc_params(Input))) || {Input, Expected} <- Cases ]. %%%=================================================================== %%% Domain name functions %%%=================================================================== decode_dname_2_ptr_test_() -> Cases = [{<<7, 101, 120, 97, 109, 112, 108, 101, 0>>, <<3:2, 0:14>>}], [ ?_assertEqual({<<"example">>, <<>>}, decode_dname(DataBin, MsgBin)) || {MsgBin, DataBin} <- Cases ]. decode_dname_decode_loop_test() -> Bin = <<3:2, 0:14>>, ?assertException(throw, decode_loop, decode_dname(Bin, Bin)). decode_dname_bad_pointer_test() -> Case = <<3:2, 42:14>>, ?assertException(throw, bad_pointer, decode_dname(Case, Case)). encode_dname_1_test_() -> Cases = [ {"example", <<7, 101, 120, 97, 109, 112, 108, 101, 0>>}, {<<"example">>, <<7, 101, 120, 97, 109, 112, 108, 101, 0>>} ], [?_assertEqual(Expect, encode_dname(Input)) || {Input, Expect} <- Cases]. encode_dname_3_test_() -> {Bin, _CompMap} = encode_dname(gb_trees:empty(), 0, <<"example">>), ?_assertEqual(<<7, 101, 120, 97, 109, 112, 108, 101, 0>>, Bin). encode_dname_4_test_() -> {Bin0, CM0} = encode_dname(<<>>, gb_trees:empty(), 0, <<"example">>), {Bin1, _} = encode_dname(Bin0, CM0, byte_size(Bin0), <<"example">>), {Bin2, _} = encode_dname(Bin0, CM0, byte_size(Bin0), <<"EXAMPLE">>), MP = (1 bsl 14), MPB = <<0:MP/unit:8>>, {_, CM1} = encode_dname(MPB, gb_trees:empty(), MP, <<"example">>), Cases = [ {<<7, 101, 120, 97, 109, 112, 108, 101, 0>>, Bin0}, {<<7, 101, 120, 97, 109, 112, 108, 101, 0, 192, 0>>, Bin1}, {Bin1, Bin2}, {gb_trees:empty(), CM1} ], [?_assertEqual(Expect, Result) || {Expect, Result} <- Cases]. dname_to_labels_test_() -> Cases = [ {"", []}, {".", []}, {<<>>, []}, {<<".">>, []}, {"a.b.c", [<<"a">>, <<"b">>, <<"c">>]}, {<<"a.b.c">>, [<<"a">>, <<"b">>, <<"c">>]}, {"a.b.c.", [<<"a">>, <<"b">>, <<"c">>]}, {<<"a.b.c.">>, [<<"a">>, <<"b">>, <<"c">>]}, {"a\\.b.c", [<<"a.b">>, <<"c">>]}, {<<"a\\.b.c">>, [<<"a.b">>, <<"c">>]} ], [?_assertEqual(Expect, dname_to_labels(Arg)) || {Arg, Expect} <- Cases]. labels_to_dname_test_() -> Cases = [ {[<<"a">>, <<"b">>, <<"c">>], <<"a.b.c">>}, {[<<"a.b">>, <<"c">>], <<"a\\.b.c">>} ], [?_assertEqual(Expect, labels_to_dname(Arg)) || {Arg, Expect} <- Cases]. dname_to_upper_test_() -> Cases = [{"Y", "Y"}, {"y", "Y"}, {<<"Y">>, <<"Y">>}, {<<"y">>, <<"Y">>}], [?_assertEqual(Expect, dname_to_upper(Arg)) || {Arg, Expect} <- Cases]. dname_to_lower_test_() -> Cases = [{"Y", "y"}, {"y", "y"}, {<<"Y">>, <<"y">>}, {<<"y">>, <<"y">>}], [?_assertEqual(Expect, dname_to_lower(Arg)) || {Arg, Expect} <- Cases]. dname_preserve_dot_test_() -> Query = #dns_query{name = <<"example\\.com">>, class = 1, type = 1}, Message = #dns_message{qc = 1, questions = [Query]}, Encoded = dns:encode_message(Message), Decoded = dns:decode_message(Encoded), ReEncoded = dns:encode_message(Decoded), ReDecoded = dns:decode_message(ReEncoded), [ ?_assertEqual(Message, Decoded), ?_assertEqual(Encoded, ReEncoded), ?_assertEqual(Message, ReDecoded) ]. %%%=================================================================== %%% Term functions %%%=================================================================== class_name_test_() -> {ok, Cases} = file:consult(filename:join(prefix(), "rrdata_wire_samples.txt")), Classes = sets:to_list(sets:from_list([C || {C, _, _} <- Cases])), [?_assertEqual(true, is_binary(dns:class_name(C))) || C <- Classes]. type_name_test_() -> {ok, Cases} = file:consult(filename:join(prefix(), "rrdata_wire_samples.txt")), Types = sets:to_list(sets:from_list([T || {_, T, _} <- Cases])), [?_assertEqual(T =/= 999, is_binary(dns:type_name(T))) || T <- Types]. alg_terms_test_() -> Cases = [ ?DNS_ALG_DSA, ?DNS_ALG_NSEC3DSA, ?DNS_ALG_RSASHA1, ?DNS_ALG_NSEC3RSASHA1, ?DNS_ALG_RSASHA256, ?DNS_ALG_RSASHA512 ], [?_assertEqual(true, is_binary(dns:alg_name(Alg))) || Alg <- Cases]. %%%=================================================================== -endif.