%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(inet_res_SUITE).

-include_lib("common_test/include/ct.hrl").

-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/src/inet_dns.hrl").

-include("kernel_test_lib.hrl").


-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
	 init_per_group/2, end_per_group/2,
	 init_per_testcase/2, end_per_testcase/2
        ]).
-export([basic/1, name_addr_and_cached/1, resolve/1,
         edns0/1, txt_record/1, files_monitor/1,
	 nxdomain_reply/1, last_ms_answer/1, intermediate_error/1,
         servfail_retry_timeout_default/1, servfail_retry_timeout_1000/1,
         label_compression_limit/1
        ]).
-export([
	 gethostbyaddr/0, gethostbyaddr/1,
	 gethostbyaddr_v6/0, gethostbyaddr_v6/1,
	 gethostbyname/0, gethostbyname/1,
	 gethostbyname_v6/0, gethostbyname_v6/1,
	 getaddr/0, getaddr/1,
	 getaddr_v6/0, getaddr_v6/1,
	 ipv4_to_ipv6/0, ipv4_to_ipv6/1,
	 host_and_addr/0, host_and_addr/1
	]).

-define(RUN_NS, "run-ns").
-define(LOG_FILE, "ns.log").

%% This test suite use a script ?RUN_NS that tries to start
%% a temporary local nameserver BIND 8 or 9 that must be installed
%% on your machine.
%%
%% For example, on Ubuntu 16.04 / 18.04, as root:
%%     apt-get install bind9
%% Now, that is not enough since Apparmor will not allow
%% the nameserver daemon /usr/sbin/named to read from the test directory.
%% Assuming that you run tests in /ldisk/daily_build, and still on
%% Ubuntu 14.04, make /etc/apparmor.d/local/usr.sbin.named contain:
%%     /ldisk/daily_build/** r,
%% And yes; the trailing comma must be there...
%% And yes; create the file if it does not exist.
%% And yes; restart the apparmor daemon using "service apparmor restart"


suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap,{minutes,1}}].

all() -> 
    [basic, resolve, name_addr_and_cached,
     edns0, txt_record, files_monitor,
     nxdomain_reply, last_ms_answer,
     intermediate_error,
     servfail_retry_timeout_default, servfail_retry_timeout_1000,
     label_compression_limit,
     gethostbyaddr, gethostbyaddr_v6, gethostbyname,
     gethostbyname_v6, getaddr, getaddr_v6, ipv4_to_ipv6,
     host_and_addr].

groups() -> 
    [].

init_per_suite(Config0) ->

    ?P("init_per_suite -> entry with"
       "~n      Config: ~p"
       "~n      Nodes:  ~p", [Config0, erlang:nodes()]),

    case ?LIB:init_per_suite(Config0) of
        {skip, _} = SKIP ->
            SKIP;

        Config1 when is_list(Config1) ->

            ?P("init_per_suite -> end when "
               "~n      Config: ~p", [Config1]),

            Config1
    end.

end_per_suite(Config0) ->

    ?P("end_per_suite -> entry with"
       "~n      Config: ~p"
       "~n      Nodes:  ~p", [Config0, erlang:nodes()]),

    Config1 = ?LIB:end_per_suite(Config0),

    ?P("end_per_suite -> "
       "~n      Nodes: ~p", [erlang:nodes()]),

    Config1. %% We don't actually need to update or return config


init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.

zone_dir(TC) ->
    case TC of
	basic                -> otptest;
	name_addr_and_cached -> otptest;
	resolve              -> otptest;
	edns0                -> otptest;
	files_monitor        -> otptest;
	nxdomain_reply       -> otptest;
	last_ms_answer       -> otptest;
        intermediate_error   ->
            {internal,
             #{rcode => ?REFUSED}};
        servfail_retry_timeout_default ->
            {internal,
             #{rcode => ?SERVFAIL, etd => 1500}};
        servfail_retry_timeout_1000 ->
            {internal,
             #{rcode => ?SERVFAIL, etd => 1000}};
	_ -> undefined
    end.

init_per_testcase(Func, Config) ->

    ?P("init_per_testcase -> entry with"
       "~n      Func:   ~p"
       "~n      Config: ~p", [Func, Config]),

    PrivDir = proplists:get_value(priv_dir, Config),
    DataDir = proplists:get_value(data_dir, Config),
    try ns_init(zone_dir(Func), PrivDir, DataDir) of
	NsSpec ->
            ?P("init_per_testcase -> get resolver lookup"),
	    Lookup = inet_db:res_option(lookup),
            ?P("init_per_testcase -> set file:dns"),
	    inet_db:set_lookup([file,dns]),
	    case NsSpec of
		{_,{IP,Port},_} ->
                    ?P("init_per_testcase -> insert alt nameserver ~p:~w",
                       [IP, Port]),
		    inet_db:ins_alt_ns(IP, Port);
		_ -> ok
	    end,
            %% dbg:tracer(),
            %% dbg:p(all, c),
            %% dbg:tpl(inet_res, query_nss_res, cx),
            ?P("init_per_testcase -> done:"
               "~n      NsSpec: ~p"
               "~n      Lookup: ~p", [NsSpec, Lookup]),
	    [{nameserver, NsSpec}, {res_lookup, Lookup} | Config]
    catch
	SkipReason ->
            ?P("init_per_testcase -> caught:"
               "~n      SkipReason: ~p", [SkipReason]),
	    {skip, SkipReason}
    end.

end_per_testcase(_Func, Config) ->
    inet_db:set_lookup(proplists:get_value(res_lookup, Config)),
    NsSpec = proplists:get_value(nameserver, Config),
    case NsSpec of
	{_,{IP,Port},_} ->
	    inet_db:del_alt_ns(IP, Port);
	_ -> ok
    end,
    %% dbg:stop(),
    ns_end(NsSpec, proplists:get_value(priv_dir, Config)).


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Nameserver control

ns(Config) ->
    {_ZoneDir,NS,_P} = proplists:get_value(nameserver, Config),
    NS.

ns_init(ZoneDir, PrivDir, DataDir) ->

    ?P("ns_init -> entry with"
       "~n      ZoneDir: ~p"
       "~n      PrivDir: ~p"
       "~n      DataDir: ~p", [ZoneDir, PrivDir, DataDir]),

    case {os:type(),ZoneDir} of
        {_,{internal,ServerSpec}} ->
            ns_start_internal(ServerSpec);
	{{unix,_},undefined} ->
            ?P("ns_init -> nothing"),
            undefined;
	{{unix,_},otptest} ->
            ?P("ns_init -> prepare start"),
	    PortNum = case {os:type(),os:version()} of
			  {{unix,solaris},{M,V,_}} when M =< 5, V < 10 ->
			      11895 + rand:uniform(100);
			  _ ->
			      S = ok(gen_udp:open(0, [{reuseaddr,true}])),
			      PNum = ok(inet:port(S)),
			      gen_udp:close(S),
			      PNum
		      end,
            ?P("ns_init -> use port number ~p", [PortNum]),
	    RunNamed = filename:join(DataDir, ?RUN_NS),
            ?P("ns_init -> use named ~p", [RunNamed]),
	    NS = {{127,0,0,1},PortNum},
            ?P("ns_init -> try open port (exec)"),
	    P = erlang:open_port({spawn_executable,RunNamed},
				 [{cd,PrivDir},
				  {line,80},
				  {args,["127.0.0.1",
					 integer_to_list(PortNum),
					 atom_to_list(ZoneDir)]},
				  stderr_to_stdout,
				  eof]),
            ?P("ns_init -> port ~p", [P]),
	    ns_start(ZoneDir, PrivDir, NS, P);
	_ ->
	    throw("Only run on Unix")
    end.

ns_start(ZoneDir, PrivDir, NS, P) ->

    ?P("ns_start -> await message"),

    case ns_collect(P) of
	eof ->
            ?P("ns_start -> eof"),
	    erlang:error(eof);
	"Running: "++_ ->
            ?P("ns_start -> running"),
	    {ZoneDir,NS,P};
	"Error: "++Error ->
            ?P("ns_start -> error: "
               "~n      ~p", [Error]),
	    ns_printlog(filename:join([PrivDir,ZoneDir,?LOG_FILE])),
	    throw(Error);
	_X ->
            ?P("ns_start -> retry"),
	    ns_start(ZoneDir, PrivDir, NS, P)
    end.


ns_start_internal(ServerSpec) ->

    ?P("ns_start_internal -> entry with"
       "~n      ServerSpec: ~p", [ServerSpec]),

    Parent = self(),
    Tag = make_ref(),
    {P,Mref} =
        spawn_monitor(
          fun () ->
                  _ = process_flag(trap_exit, true),
                  IP = {127,0,0,1},
                  SocketOpts = [{ip,IP},binary,{active,once}],
                  S = ok(gen_udp:open(0, SocketOpts)),
                  Port = ok(inet:port(S)),
                  ParentMref = monitor(process, Parent),
                  Parent ! {Tag,{IP,Port},self()},
                  ns_internal(ServerSpec, ParentMref, Tag, S)
          end),
    receive
        {Tag,_NS,P} = NsSpec ->
            ?P("ns_start_internal -> ~p started", [P]),
            demonitor(Mref, [flush]),
            NsSpec;
        {'DOWN',Mref,_,_,Reason} ->
            ?P("ns_start_internal -> failed start:"
               "~n      ~p", [Reason]),
            exit({ns_start_internal,Reason})
    end.

ns_end(undefined, _PrivDir) -> undefined;
ns_end({ZoneDir,_NS,P}, PrivDir) when is_port(P) ->
    port_command(P, ["quit",io_lib:nl()]),
    ns_stop(P),
    ns_printlog(filename:join([PrivDir,ZoneDir,"ns.log"])),
    ok;
ns_end({Tag,_NS,P}, _PrivDir) when is_pid(P) ->
    Mref = erlang:monitor(process, P),
    P ! Tag,
    receive
        {'DOWN',Mref,_,_,Reason} ->
            Reason = normal,
            ok
    end.

ns_stop(P) ->
    case ns_collect(P) of
	eof ->
	    erlang:port_close(P);
	_ ->
	    ns_stop(P)
    end.

ns_collect(P) ->
    ns_collect(P, []).
ns_collect(P, Buf) ->
    receive
	{P,{data,{eol,L}}} ->
	    Line = lists:flatten(lists:reverse(Buf, [L])),
	    ?P("collected: ~s", [Line]),
	    Line;
	{P,{data,{noeol,L}}} ->
	    ns_collect(P, [L|Buf]);
	{P,eof} ->
	    eof
    end.

ns_printlog(Fname) ->
    ?P("Name server log file contents:"),
    case file:read_file(Fname) of
	{ok,Bin} ->
	    io:format("~s~n", [Bin]);
	_ ->
	    ok
    end.

%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Internal name server

ns_internal(ServerSpec, Mref, Tag, S) ->
    ?P("ns-internal -> await message"),
    receive
        {'DOWN',Mref,_,_,Reason} ->
            ?P("ns-internal -> received DOWN: "
               "~n      ~p", [Reason]),
            exit(Reason);
        Tag ->
            ?P("ns-internal -> received tag: done"),
            ok;
        {udp,S,IP,Port,Data} ->
            ?P("ns-internal -> received UDP message"),
            Req = ok(inet_dns:decode(Data)),
            {Resp, ServerSpec2} = ns_internal(ServerSpec, Req),
            RespData = inet_dns:encode(Resp),
            _ = ok(gen_udp:send(S, IP, Port, RespData)),
            _ = ok(inet:setopts(S, [{active,once}])),
            ns_internal(ServerSpec2, Mref, Tag, S)
    end.

ns_internal(#{rcode := Rcode,
              ts    := TS0,
              etd   := ETD} = ServerSpec, Req) ->
    ?P("ns-internal -> request received (time validation)"),
    TS1    = timestamp(),
    Hdr    = inet_dns:msg(Req, header),
    Opcode = inet_dns:header(Hdr, opcode),
    Id     = inet_dns:header(Hdr, id),
    Rd     = inet_dns:header(Hdr, rd),
    %%
    Qdlist = inet_dns:msg(Req, qdlist),
    ?P("ns-internal -> time validation: "
       "~n      ETD:       ~w"
       "~n      TS1 - TS0: ~w", [ETD, TS1 - TS0]),
    RC = if ((TS1 - TS0) >= ETD) ->
                 ?P("ns-internal -> time validated"),
                 ?NOERROR;
            true ->
                 ?P("ns-internal -> time validation failed"),
                 Rcode
         end,
    Resp   = inet_dns:make_msg(
               [{header,
                 inet_dns:make_header(
                   [{id,     Id},
                    {qr,     true},
                    {opcode, Opcode},
                    {aa,     true},
                    {tc,     false},
                    {rd,     Rd},
                    {ra,     false},
                    {rcode,  RC}])},
                {qdlist, Qdlist}]),
    {Resp, ServerSpec#{ts => timestamp()}};
ns_internal(#{rcode := Rcode} = ServerSpec, Req) ->
    ?P("ns-internal -> request received"),
    Hdr    = inet_dns:msg(Req, header),
    Opcode = inet_dns:header(Hdr, opcode),
    Id     = inet_dns:header(Hdr, id),
    Rd     = inet_dns:header(Hdr, rd),
    %%
    Qdlist = inet_dns:msg(Req, qdlist),
    Resp   = inet_dns:make_msg(
               [{header,
                 inet_dns:make_header(
                   [{id,Id},
                    {qr,true},
                    {opcode,Opcode},
                    {aa,true},
                    {tc,false},
                    {rd,Rd},
                    {ra,false},
                    {rcode,Rcode}])},
                {qdlist,Qdlist}]),
    {Resp, ServerSpec#{ts => timestamp()}}.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Behaviour modifying nameserver proxy

proxy_start(TC, {NS,P}) ->
    Tag = make_ref(),
    Parent = self(),
    Pid =
	spawn_link(
	  fun () ->
		  try proxy_start(TC, NS, P, Parent, Tag)
		  catch
                      C:X:Stacktrace ->
			  ?P("~p Failed starting proxy: "
                             "~n      Class:      ~w"
                             "~n      Error:      ~p"
                             "~n      Stacktrace: ~p",
                             [self(), C, X, Stacktrace])
		  end
	  end),
    receive {started,Tag,Port} ->
	    ProxyNS = {{127,0,0,1},Port},
	    {proxy,Pid,Tag,ProxyNS}
    end.

proxy_start(TC, NS, P, Parent, Tag) ->
    {ok,Outbound} = gen_udp:open(0, [binary]),
    ok = gen_udp:connect(Outbound, NS, P),
    {ok,Inbound} = gen_udp:open(0, [binary]),
    {ok,Port} = inet:port(Inbound),
    Parent ! {started,Tag,Port},
    proxy(TC, Outbound, NS, P, Inbound).


%% To provoke the last_ms_answer bug (OTP-9221) the proxy
%% * Relays the query to the right nameserver
%% * Intercepts the reply but holds it until the timer that
%%   was started when receiving the query fires.
%% * Repeats the reply with incorrect query ID a number of
%%   times with a short interval.
%% * Sends the correct reply, to give a correct test result
%%   after bug correction.
%%
%% The repetition of an incorrect answer with tight interval will keep
%% inet_res in an inner loop in the code that decrements the remaining
%% time until it hits 0 which triggers a crash, if the outer timeout
%% parameter to inet_res:resolve is so short that it runs out during
%% these repetitions.
proxy(last_ms_answer, Outbound, NS, P, Inbound) ->
    receive
	{udp,Inbound,SrcIP,SrcPort,Data} ->
	    Time =
		inet_db:res_option(timeout) div inet_db:res_option(retry),
	    Tag = erlang:make_ref(),
	    erlang:send_after(Time - 10, self(), {time,Tag}),
	    ok = gen_udp:send(Outbound, NS, P, Data),
	    receive
		{udp,Outbound,NS,P,Reply} ->
		    {ok,Msg} = inet_dns:decode(Reply),
		    Hdr = inet_dns:msg(Msg, header),
		    Id = inet_dns:header(Hdr, id),
		    BadHdr =
			inet_dns:make_header(Hdr, id, (Id+1) band 16#ffff),
		    BadMsg = inet_dns:make_msg(Msg, header, BadHdr),
		    BadReply = inet_dns:encode(BadMsg),
		    receive
			{time,Tag} ->
			    proxy__last_ms_answer(
			      Inbound, SrcIP, SrcPort, BadReply, Reply, 30)
		    end
	    end
    end.

proxy__last_ms_answer(Socket, IP, Port, _, Reply, 0) ->
    ok = gen_udp:send(Socket, IP, Port, Reply);
proxy__last_ms_answer(Socket, IP, Port, BadReply, Reply, N) ->
    ok = gen_udp:send(Socket, IP, Port, BadReply),
    receive after 1 -> ok end,
    proxy__last_ms_answer(Socket, IP, Port, BadReply, Reply, N-1).

proxy_wait({proxy,Pid,_,_}) ->
    Mref = erlang:monitor(process, Pid),
    receive {'DOWN',Mref,_,_,_} -> ok end.

proxy_ns({proxy,_,_,ProxyNS}) -> ProxyNS.


%%
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Lookup an A record with different API functions.
basic(Config) when is_list(Config) ->
    ?P("begin"),
    NS = ns(Config),
    Name = "ns.otptest",
    NameC = caseflip(Name),
    NameD = NameC ++ ".",
    IP1 = {127,0,0,253},
    IP2 = {127,0,0,254},
    %%
    %% nslookup
    {ok,Msg1} = inet_res:nslookup(Name, in, a, [NS]),
    ?P("nslookup with ~p: ~n      ~p", [Name, Msg1]),
    [RR1, RR2] = lists:sort(inet_dns:msg(Msg1, anlist)),
    IP1 = inet_dns:rr(RR1, data),
    IP2 = inet_dns:rr(RR2, data),
    Bin1 = inet_dns:encode(Msg1),
    %%io:format("Bin1 = ~w~n", [Bin1]),
    {ok,Msg1} = inet_dns:decode(Bin1),
    %% Now with scrambled case
    {ok,Msg1b} = inet_res:nslookup(NameC, in, a, [NS]),
    ?P("nslookup with ~p: ~n      ~p", [NameC, Msg1b]),
    [RR1b, RR2b] = lists:sort(inet_dns:msg(Msg1b, anlist)),
    IP1 = inet_dns:rr(RR1b, data),
    IP2 = inet_dns:rr(RR2b, data),
    Bin1b = inet_dns:encode(Msg1b),
    %%io:format("Bin1b = ~w~n", [Bin1b]),
    {ok,Msg1b} = inet_dns:decode(Bin1b),
    true =
	(tolower(inet_dns:rr(RR1, domain))
	 =:= tolower(inet_dns:rr(RR1b, domain))),
    true =
	(tolower(inet_dns:rr(RR2, domain))
	 =:= tolower(inet_dns:rr(RR2b, domain))),
    %%
    %% resolve
    {ok,Msg2} = inet_res:resolve(Name, in, a, [{nameservers,[NS]},verbose]),
    ?P("resolve with ~p: ~n      ~p", [Name, Msg2]),
    [RR1c, RR2c] = lists:sort(inet_dns:msg(Msg2, anlist)),
    IP1 = inet_dns:rr(RR1c, data),
    IP2 = inet_dns:rr(RR2c, data),
    Bin2 = inet_dns:encode(Msg2),
    %%io:format("Bin2 = ~w~n", [Bin2]),
    {ok,Msg2} = inet_dns:decode(Bin2),
    %% Now with scrambled case
    {ok,Msg2b} = inet_res:resolve(NameC, in, a, [{nameservers,[NS]},verbose]),
    ?P("resolve with ~p: ~n      ~p", [NameC, Msg2b]),
    [RR1d, RR2d] = lists:sort(inet_dns:msg(Msg2b, anlist)),
    IP1 = inet_dns:rr(RR1d, data),
    IP2 = inet_dns:rr(RR2d, data),
    Bin2b = inet_dns:encode(Msg2b),
    %%io:format("Bin2b = ~w~n", [Bin2b]),
    {ok,Msg2b} = inet_dns:decode(Bin2b),
    true =
	(tolower(inet_dns:rr(RR1c, domain))
	  =:= tolower(inet_dns:rr(RR1d, domain))),
    true =
	(tolower(inet_dns:rr(RR2c, domain))
	  =:= tolower(inet_dns:rr(RR2d, domain))),
    ?P("resolve \"127.0.0.1\"~n", []),
    {ok, Msg3} =
        inet_res:resolve("127.0.0.1", in, a, [{nameservers,[NS]},verbose]),
    [] = inet_dns:msg(Msg3, anlist),
    {ok, Msg4} =
        inet_res:resolve("127.0.0.1", in, ptr, [{nameservers,[NS]},verbose]),
    [RR4] = inet_dns:msg(Msg4, anlist),
    "1.0.0.127.in-addr.arpa" = inet_dns:rr(RR4, domain),
    "test1-78901234567890123456789012345678.otptest" =
        inet_dns:rr(RR4, data),
    %%
    %% lookup
    ?P("lookup"),
    [IP1, IP2] =
        lists:sort(
          inet_res:lookup(Name, in, a, [{nameservers,[NS]},verbose])),
    [IP1, IP2] =
        lists:sort(
          inet_res:lookup(NameC, in, a, [{nameservers,[NS]},verbose])),
    [IP1, IP2] =
        lists:sort(
          inet_res:lookup(NameD, in, a, [{nameservers,[NS]},verbose])),
    %%
    %% gethostbyname
    ?P("gethostbyname"),
    {ok,#hostent{h_addr_list=IPs1}} = inet_res:gethostbyname(Name),
    [IP1, IP2] = lists:sort(IPs1),
    {ok,#hostent{h_addr_list=IPs2}} = inet_res:gethostbyname(NameC),
    [IP1, IP2] = lists:sort(IPs2),
    %%
    %% getbyname
    ?P("getbyname"),
    {ok,#hostent{h_addr_list=IPs3}} = inet_res:getbyname(Name, a),
    [IP1, IP2] = lists:sort(IPs3),
    {ok,#hostent{h_addr_list=IPs4}} = inet_res:getbyname(NameC, a),
    [IP1, IP2] = lists:sort(IPs4),
    ?P("end"),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Check that lookup of names and addresses works the same
%% also when cached, by simply repeating the lookups

name_addr_and_cached(Config) when is_list(Config) ->
    ?P("begin"),

%%%    dbg:tracer(),
%%%    dbg:p(all, c),
%%%    dbg:tpl(inet_res, do_query, cx),

    NS = ns(Config),
    Domain = "otptest",
    Options =
        [{lookup, [dns]},
         {resolv_conf, []},
         {hosts_file, []},
         {domain, Domain},
         {nameservers, [NS]},
         {search, [Domain]},
         {alt_nameservers, []},
         {inet6, false},
         {usevc, false},
         {edns, 0}],
    SavedOptions =
        [{Option, inet_db:res_option(Option)}
         || {Option, _Value} <- Options],
    [inet_db:res_option(Option, Value)
     || {Option, Value} <- Options],
    try
        ?P("first pass"),
        %% Flip character case randomly
        name_addr(Domain, fun caseflip/1),
        %%
        ?P("second pass"),
        %% Use only character upper case,
        %% should get identical results from the cache
        name_addr(Domain, fun toupper/1)
    after
        [ok = inet_db:res_option(Option, Value)
         || {Option, Value} <- SavedOptions]
    end,
    ?P("done"),
    ok.

name_addr(Domain, CFlip) ->
%%    RDomain4 = "0.0.127.in-addr.arpa",
%%    RDomain6 = "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
    H4 =
        #hostent{
           h_addrtype = inet,
           h_length = 4},
    H6 =
        #hostent{
           h_addrtype = inet6,
           h_length = 16},
    H_mx              = CFlip("mx"),
    H_mx_             = CFlip("mx."),
    IP4_mx            = {127,0,0,10},
    %%
    H_resolve         = CFlip("resolve"),
    H_resolve_        = CFlip("resolve."),
    H_cname_resolve   = CFlip("cname.resolve"),
    H_cname_resolve_  = CFlip("cname.resolve."),
    %%
    IP4_resolve       = {127,0,0,28},
    IP6_resolve       = {0,0,0,0,0,0,127 bsl 8,28},
    IP64_resolve      = {0,0,0,0,0,16#ffff,127 bsl 8,28},
    %%
    H_ns              = CFlip("ns"),
    H_ns_             = CFlip("ns."),
    IP4_ns_1          = {127,0,0,253},
    IP4_ns_2          = {127,0,0,254},
    IP6_ns_1          = {0,0,0,0,0,0,127 bsl 8,253},
    IP6_ns_2          = {0,0,0,0,0,0,127 bsl 8,254},
    IP64_ns_1         = {0,0,0,0,0,16#ffff,127 bsl 8,253},
    IP64_ns_2         = {0,0,0,0,0,16#ffff,127 bsl 8,254},
    Lookups =
        %% The search list is [Domain] so a lookup of a short
        %% name should return the fully qualified name with
        %% Domain appended.
        %%
        %% Lookup results should have the same character case
        %% as the query.  Both for short (search) lookups and
        %% for full name lookups.
        %%
        %% Lookup via a CNAME record should return the
        %% result as stored in DNS but the CNAME should be
        %% returned as an alias with the query character case
        %% preserved.
        %%
        %% Address lookups (reverse/PTR lookups) can only
        %% return the one address we looked up and the
        %% fully qualified name as in DNS, and no aliases.
        %%
        %% IPv6 address lookups in ::ffff.A.B.C.D
        %% (IPv4-compatible IPv6 addresses) should be
        %% internally done as IPv4 lookups by the client,
        %% without the caller noticing.
        %%
        [{{H_mx, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_mx++[$.|Domain],
            h_addr_list = [IP4_mx]}},
         {{H_mx_++Domain, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_mx_++Domain,
            h_addr_list = [IP4_mx]}},
         {{H_resolve, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_resolve++[$.|Domain],
            h_addr_list = [IP4_resolve]}},
         {{H_resolve_++Domain, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_resolve_++Domain,
            h_addr_list = [IP4_resolve]}},
         {{H_cname_resolve, inet}, fun lower_h_name/1, ?LINE,
          H4#hostent{
            h_name = "resolve."++Domain,
            h_aliases = [H_cname_resolve++[$.|Domain]],
            h_addr_list = [IP4_resolve]}},
         {{H_cname_resolve_++Domain, inet}, fun lower_h_name/1, ?LINE,
          H4#hostent{
            h_name = "resolve."++Domain,
            h_aliases = [H_cname_resolve_++Domain],
            h_addr_list = [IP4_resolve]}},
         %%
         {{H_ns, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_ns++[$.|Domain],
            h_addr_list = [IP4_ns_1,IP4_ns_2]}},
         {{H_ns_++Domain, inet}, fun norm/1, ?LINE,
          H4#hostent{
            h_name = H_ns_++Domain,
            h_addr_list = [IP4_ns_1,IP4_ns_2]}},
         %%
         {IP4_ns_2, fun norm/1, ?LINE,
          H4#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP4_ns_2]}},
         {IP4_ns_1, fun norm/1, ?LINE,
          H4#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP4_ns_1]}},
         {IP4_mx, fun norm/1, ?LINE,
          H4#hostent{
            h_name = "mx."++Domain,
            h_addr_list = [IP4_mx]}},
         {IP4_mx, fun norm/1, ?LINE,
          H4#hostent{
            h_name = "mx."++Domain,
            h_addr_list = [IP4_mx]}},
         %%
         %%
         %%
         {{H_resolve_++Domain, inet6}, fun norm/1, ?LINE,
          H6#hostent{
            h_name = H_resolve_++Domain,
            h_addr_list = [IP6_resolve]}},
         {{H_resolve, inet6}, fun norm/1, ?LINE,
          H6#hostent{
            h_name = H_resolve++[$.|Domain],
            h_addr_list = [IP6_resolve]}},
         {{H_cname_resolve, inet6}, fun lower_h_name/1, ?LINE,
          H6#hostent{
            h_name = "resolve."++Domain,
            h_aliases = [H_cname_resolve++[$.|Domain]],
            h_addr_list = [IP6_resolve]}},
         {{H_cname_resolve_++Domain, inet6}, fun lower_h_name/1, ?LINE,
          H6#hostent{
            h_name = "resolve."++Domain,
            h_aliases = [H_cname_resolve_++Domain],
            h_addr_list = [IP6_resolve]}},
         {IP6_resolve, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "resolve."++Domain,
            h_addr_list = [IP6_resolve]}},
         {IP64_resolve, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "resolve."++Domain,
            h_addr_list = [IP64_resolve]}},
         %%
         {{H_ns, inet6}, fun norm/1, ?LINE,
          H6#hostent{
            h_name = H_ns++[$.|Domain],
            h_addr_list =
                [IP6_ns_1,
                 IP6_ns_2]}},
         {{H_ns_++Domain, inet6}, fun norm/1, ?LINE,
          H6#hostent{
            h_name = H_ns_++Domain,
            h_addr_list =
                [IP6_ns_1,
                 IP6_ns_2]}},
         {IP6_ns_1, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP6_ns_1]}},
         {IP6_ns_2, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP6_ns_2]}},
         {IP64_ns_1, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP64_ns_1]}},
         {IP64_ns_2, fun norm/1, ?LINE,
          H6#hostent{
            h_name = "ns."++Domain,
            h_addr_list = [IP64_ns_2]}}
         %%
         %%
         %%
        ],
    Results =
        [case Target of
             {H, T} ->
                 inet_res:gethostbyname(H, T);
             Addr ->
                 inet_res:gethostbyaddr(Addr)
         end || {Target, _TFun, _Line, _HE} <- Lookups],
    ?P("inet_cache: ~p~n", [ets:tab2list(inet_cache)]),
    [] = merge_results(Lookups, Results),
    ok.

lower_h_name(#hostent{h_name = HName} = HE) ->
    norm(HE#hostent{h_name = tolower(HName)}).

%%%lower_h_aliases(#hostent{h_aliases = HAliases} = HE) ->
%%%    HE#hostent{
%%%      h_aliases = [tolower(HAlias) || HAlias <- HAliases]}.

norm(#hostent{h_addr_list = Addrs} = HE) ->
    HE#hostent{h_addr_list = lists:sort(Addrs)}.

merge_results([], []) ->
    [];
merge_results([{H_T, TFun, Line, HE} | Lookups], [Result | Results]) ->
    case
        case Result of
            {ok, ResultHE} ->
                {ok, TFun(ResultHE)};
            _ ->
                Result
        end
    of
        {ok, HE} ->
            merge_results(Lookups, Results);
        Other ->
            [{{Line, H_T, HE}, Other} | merge_results(Lookups, Results)]
    end.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Lookup different records using resolve/2..4.
resolve(Config) when is_list(Config) ->
    ?P("begin"),
    Class = in,
    NS = ns(Config),
    Domain = "otptest",
    RDomain4 = "0.0.127.in-addr.arpa",
    RDomain6 = "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
    Name = "resolve."++Domain,
    L = [{a,Name,[{a,{127,0,0,28}}],undefined},
	 {aaaa,Name,[{aaaa,{0,0,0,0,0,0,32512,28}}],undefined},
	 {cname,"cname."++Name,[{cname,Name}],undefined},
	 {a,"cname."++Name,[{cname,Name},{a,{127,0,0,28}}],undefined},
	 {ns,"ns."++Name,[],[{ns,Name}]},
	 {soa,Domain,
          undefined,
          [{soa,{"ns.otptest","lsa\\.soa.otptest",1,60,10,300,30}}]},
	 %% WKS: protocol TCP (6), services (bits) TELNET (23) and SMTP (25)
	 {wks,"wks."++Name,[{wks,{{127,0,0,28},6,<<0,0,1,64>>}}],undefined},
	 {ptr,"28."++RDomain4,[{ptr,Name}],undefined},
	 {ptr,"c.1.0.0.0.0.f.7."++RDomain6,[{ptr,Name}],undefined},
	 {hinfo,Name,[{hinfo,{"BEAM","Erlang/OTP"}}],undefined},
	 {mx,RDomain4,[{mx,{10,"mx."++Domain}}],undefined},
	 {srv,"_srv._tcp."++Name,[{srv,{10,3,4711,Name}}],undefined},
	 {naptr,"naptr."++Name,
	  [{naptr,{10,5,"s","http","","_srv._tcp."++Name}}],
	  undefined},
	 {txt,"txt."++Name,
	  [{txt,["Hej ","du ","glade "]},{txt,["ta ","en ","spade!"]}],
	  undefined},
	 {mb,"mb."++Name,[{mb,"mx."++Name}],undefined},
	 {mg,"mg."++Name,[{mg,"lsa\\.mg."++Domain}],undefined},
	 {mr,"mr."++Name,[{mr,"lsa\\.mr."++Domain}],undefined},
	 {minfo,"minfo."++Name,
	  [{minfo,{"minfo-owner."++Name,"minfo-bounce."++Name}}],
	  undefined},
         {uri,"uri."++Name,[{uri,{10,3,"http://erlang.org"}}],undefined},
         {caa,"caa."++Name,
          [{caa,{1,"iodef","http://iodef.erlang.org"}}],
          undefined},
	 {any,"cname."++Name,[{cname,Name}],undefined},
	 {any,Name,
	  #{ {a,{127,0,0,28}} => [],
             {aaaa,{0,0,0,0,0,0,32512,28}} => [],
             {hinfo,{"BEAM","Erlang/OTP"}} => [] },
	  undefined}
	],
    ?P("resolve -> with edns 0"),
    resolve(Class, [{edns,0},{nameservers,[NS]}], L),
    ?P("resolve -> with edns false"),
    resolve(Class, [{edns,false},{nameservers,[NS]}], L),
    %% Again, to see ensure the cache does not mess things up
    ?P("resolve -> with edns 0 (again)"),
    resolve(Class, [{edns,0},{nameservers,[NS]}], L),
    ?P("resolve -> with edns false (again)"),
    Res = resolve(Class, [{edns,false},{nameservers,[NS]}], L),
    ?P("resolve -> done: ~p", [Res]),
    Res.

resolve(_Class, _Opts, []) ->
    ?P("resolve -> done"),
    ok;
resolve(Class, Opts, [{Type,Nm,Answers,Authority}=Q|Qs]) ->
    ?P("resolve ->"
       "~n      Query:   ~p"
       "~n      Options: ~p", [Q, Opts]),
    {Name,NameC} =
	case erlang:phash2(Q) band 4 of
	    0 ->
		{Nm,caseflip(Nm)};
	    _ ->
		{caseflip(Nm),Nm}
	end,
    NormAnswers = normalize_rrs(Answers),
    NormNSs = normalize_rrs(Authority),
    ?P("resolve -> resolve with ~p", [Name]),
    {ok,Msg} = inet_res:resolve(Name, Class, Type, Opts),
    check_msg(Class, Type, Msg, NormAnswers, NormNSs),
    ?P("resolve -> resolve with ~p", [NameC]),
    {ok,MsgC} = inet_res:resolve(NameC, Class, Type, Opts),
    check_msg(Class, Type, MsgC, NormAnswers, NormNSs),
    ?P("resolve -> next"),
    resolve(Class, Opts, Qs).



normalize_rrs(undefined = RRs) -> RRs;
normalize_rrs(RRList) when is_list(RRList) ->
    lists:sort([normalize_rr(RR) || RR <- RRList]);
normalize_rrs(RRs) when is_map(RRs) ->
    maps:fold(
      fun (RR, V, NormRRs) ->
              NormRRs#{(normalize_rr(RR)) => V}
      end, #{}, RRs).

normalize_rr({soa,{NS,HM,Ser,Ref,Ret,Exp,Min}}) ->
    {tolower(NS),tolower_email(HM),Ser,Ref,Ret,Exp,Min};
normalize_rr({mx,{Prio,DN}}) ->
    {Prio,tolower(DN)};
normalize_rr({srv,{Prio,Weight,Port,DN}}) ->
    {Prio,Weight,Port,tolower(DN)};
normalize_rr({naptr,{Order,Pref,Flags,Service,RE,Repl}}) ->
    {Order,Pref,Flags,Service,RE,tolower(Repl)};
normalize_rr({minfo,{RespM,ErrM}}) ->
    {tolower_email(RespM),tolower_email(ErrM)};
normalize_rr({T,MN}) when T =:= mg; T =:= mr ->
    tolower_email(MN);
normalize_rr({T,DN}) when T =:= cname; T =:= ns; T =:= ptr; T =:= mb ->
    tolower(DN);
normalize_rr(RR) ->
    RR.

check_msg(Class, Type, Msg, ExpectedAnswers, ExpectedNSs) ->
    ?P("check_msg ->"
       "~n      Type: ~p"
       "~n      Msg:  ~p", [Type,Msg]),
    NormAnList =
        normalize_rrs(
          [begin
               Class = inet_dns:rr(RR, class),
               {inet_dns:rr(RR, type),inet_dns:rr(RR, data)}
           end || RR <- inet_dns:msg(Msg, anlist)]),
    NormNsList =
           normalize_rrs(
             [begin
                  Class = inet_dns:rr(RR, class),
                  {inet_dns:rr(RR, type),inet_dns:rr(RR, data)}
              end || RR <- inet_dns:msg(Msg, nslist)]),
    case
        check_msg(ExpectedAnswers, NormAnList) andalso
        check_msg(ExpectedNSs, NormNsList)
    of
        true ->
            ok;
        false
          when Type =:= ns;
               Type =:= soa ->
            %% Some resolvers return the answer to a NS query
            %% in the answer section instead of in the authority section,
            %% and some do the same for a SOA query
            case
                check_msg(ExpectedAnswers, NormNsList) andalso
                check_msg(ExpectedNSs, NormAnList)
            of
                true ->
                    ok;
                false ->
                    error({Type,
                           {expected,ExpectedAnswers,ExpectedNSs},
                           {got,NormAnList,NormNsList}})
            end;
        false ->
            error({Type,
                   {expected,ExpectedAnswers,ExpectedNSs},
                   {got,NormAnList,NormNsList}})
    end,
    %% Test the encoder against the decoder; the least we can do
    Buf = inet_dns:encode(Msg),
    {ok,Msg} = inet_dns:decode(Buf),
    ok.

check_msg(undefined, RRs) when is_list(RRs)-> true;
check_msg(RRs1, RRs2) when is_list(RRs1), is_list(RRs2) ->
    RRs1 =:= RRs2;
check_msg(Expected, [RR|RRs]) when is_map(Expected) ->
    case Expected of
        #{RR := _} ->
            case RRs of
                []    -> true;
                [_|_] -> check_msg(Expected, RRs)
            end;
        #{} -> false
    end;
check_msg(#{}, []) -> false. % At least one has to be ok

%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Test EDNS and truncation.
edns0(Config) when is_list(Config) ->
    ?P("begin"),
    NS = ns(Config),
    Domain = "otptest",
    Filler = "-5678901234567890123456789012345678.",
    MXs = lists:sort([{10,"mx."++Domain},
		      {20,"mx1"++Filler++Domain},
		      {20,"mx2"++Filler++Domain},
		      {20,"mx3"++Filler++Domain},
		      {20,"mx4"++Filler++Domain},
		      {20,"mx5"++Filler++Domain},
		      {20,"mx6"++Filler++Domain},
		      {20,"mx7"++Filler++Domain}]),
    false = inet_db:res_option(edns), % ASSERT
    true = inet_db:res_option(udp_payload_size) >= 1280, % ASSERT
    %% These will fall back to TCP
    MXs = lists:sort(inet_res:lookup(Domain, in, mx, [{nameservers,[NS]},verbose])),
    %%
    {ok,#hostent{h_addr_list=As}} = inet_res:getbyname(Domain++".", mx),
    MXs = lists:sort(As),
    %%
    {ok,Msg1} = inet_res:resolve(Domain, in, mx),
    MXs = lists:sort(inet_res_filter(inet_dns:msg(Msg1, anlist), in, mx)),
    %% There should be no OPT record in the answer
    [] = [RR || RR <- inet_dns:msg(Msg1, arlist),
		inet_dns:rr(RR, type) =:= opt],
    Buf1 = inet_dns:encode(Msg1),
    {ok,Msg1} = inet_dns:decode(Buf1),
    %%
    %% Use EDNS - should not need to fall back to TCP
    %% there is no way to tell from the outside.
    %%
    {ok,Msg2} = inet_res:resolve(Domain, in, mx, [{edns,0}]),
    MXs = lists:sort(inet_res_filter(inet_dns:msg(Msg2, anlist), in, mx)),
    Buf2 = inet_dns:encode(Msg2),
    {ok,Msg2} = inet_dns:decode(Buf2),
    Res = case [RR || RR <- inet_dns:msg(Msg2, arlist),
                      inet_dns:rr(RR, type) =:= opt] of
              [OptRR] ->
                  ?P("opt rr:"
                     "~n      ~p", [inet_dns:rr(OptRR)]),
                  ok;
              [] ->
                  case os:type() of
                      {unix,sunos} ->
                          case os:version() of
                              {M,V,_} when M < 5;  M == 5, V =< 8 ->
                                  %% In our test park only known platform
                                  %% with an DNS resolver that cannot do
                                  %% EDNS0.
                                  {comment,"No EDNS0"}
                          end;
                      _ ->
                          ok
                  end
          end,
    ?P("done"),
    Res.

inet_res_filter(Anlist, Class, Type) ->
    [inet_dns:rr(RR, data) || RR <- Anlist,
			      inet_dns:rr(RR, type) =:= Type,
			      inet_dns:rr(RR, class) =:= Class].


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Tests TXT records.
txt_record(Config) when is_list(Config) ->
    ?P("begin"),
    D1 = "cslab.ericsson.net",
    D2 = "mail1.cslab.ericsson.net",
    ?P("try nslookup of ~p", [D1]),
    {ok,#dns_rec{anlist=[RR1]}} = 
	inet_res:nslookup(D1, in, txt),
    ?P("RR1:"
       "~n      ~p", [RR1]),
    ?P("try nslookup of ~p", [D2]),
    {ok,#dns_rec{anlist=[RR2]}} =
	inet_res:nslookup(D2, in, txt),
    ?P("RR2:"
       "~n      ~p", [RR2]),
    #dns_rr{domain=D1, class=in, type=txt, data=A1} = RR1,
    #dns_rr{domain=D2, class=in, type=txt, data=A2} = RR2,
    case [lists:flatten(A2)] of
	A1 = [[_|_]] -> ok
    end,
    ?P("done"),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Tests monitoring of /etc/hosts and /etc/resolv.conf, but not them.
files_monitor(Config) when is_list(Config) ->
    ?P("begin"),
    Search = inet_db:res_option(search),
    HostsFile = inet_db:res_option(hosts_file),
    ResolvConf = inet_db:res_option(resolv_conf),
    Inet6 = inet_db:res_option(inet6),
    try do_files_monitor(Config)
    after
        inet_db:res_option(search, Search),
        inet_db:res_option(resolv_conf, ResolvConf),
	inet_db:res_option(hosts_file, HostsFile),
	inet_db:res_option(inet6, Inet6)
    end,
    ?P("done"),
    ok.

do_files_monitor(Config) ->
    Dir = proplists:get_value(priv_dir, Config),
    {ok,Hostname} = inet:gethostname(),
    ?P("Hostname: ~p", [Hostname]),
    FQDN =
	case inet_db:res_option(domain) of
	    "" ->
		Hostname;
	    _ ->
		Hostname++"."++inet_db:res_option(domain)
	end,
    ?P("FQDN: ~p", [FQDN]),
    HostsFile = filename:join(Dir, "files_monitor_hosts"),
    ResolvConf = filename:join(Dir, "files_monitor_resolv.conf"),
    ok = inet_db:res_option(resolv_conf, ResolvConf),
    ok = inet_db:res_option(hosts_file, HostsFile),
    [] = inet_db:res_option(search),
    %% The inet function will use its final fallback to find this host
    {ok,#hostent{h_name = Hostname,
		 h_addrtype = inet,
		 h_length = 4,
		 h_addr_list = [{127,0,0,1}]}} = inet:gethostbyname(Hostname),
    {ok,#hostent{h_name = FQDN,
		 h_addrtype = inet,
		 h_length = 4,
		 h_addr_list = [{127,0,0,1}]}} = inet:gethostbyname(FQDN),
    {error,nxdomain} = inet_res:gethostbyname(Hostname),
    {error,nxdomain} = inet_res:gethostbyname(FQDN),
    {ok,{127,0,0,10}} = inet:getaddr("mx.otptest", inet),
    {ok,{0,0,0,0,0,0,32512,28}} = inet:getaddr("resolve.otptest", inet6),
    %% The inet function will use its final fallback to find this host
    {ok,#hostent{h_name = Hostname,
		 h_addrtype = inet6,
		 h_length = 16,
		 h_addr_list = [{0,0,0,0,0,0,0,1}]}} =
	inet:gethostbyname(Hostname, inet6),
    {ok,#hostent{h_name = FQDN,
		 h_addrtype = inet6,
		 h_length = 16,
		 h_addr_list = [{0,0,0,0,0,0,0,1}]}} =
	inet:gethostbyname(FQDN, inet6),
    {error,nxdomain} = inet_res:gethostbyname("resolve"),
    %% XXX inet does not honour res_option inet6, might be a problem?
    %% therefore inet_res is called here
    ok = inet_db:res_option(inet6, true),
    {ok,#hostent{h_name = "resolve.otptest",
		 h_addrtype = inet6,
		 h_length = 16,
		 h_addr_list = [{0,0,0,0,0,0,32512,28}]}} =
	inet_res:gethostbyname("resolve.otptest"),
    {error,nxdomain} = inet_hosts:gethostbyname("files_monitor"),
    ok = file:write_file(ResolvConf, "search otptest\n"),
    ok = file:write_file(HostsFile, "::100 files_monitor\n"),
    receive after 7000 -> ok end, % RES_FILE_UPDATE_TM in inet_res.hrl is 5 s
    {ok,#hostent{h_name = "resolve.otptest",
		 h_addrtype = inet6,
		 h_length = 16,
		 h_addr_list = [{0,0,0,0,0,0,32512,28}]}} =
	inet_res:gethostbyname("resolve.otptest"),
    ["otptest"] = inet_db:res_option(search),
    {ok,#hostent{h_name = "files_monitor",
		 h_addrtype = inet6,
		 h_length = 16,
		 h_addr_list = [{0,0,0,0,0,0,0,256}]}} =
	inet_hosts:gethostbyname("files_monitor"),
    ok = inet_db:res_option(inet6, false),
    {ok,#hostent{h_name = "resolve.otptest",
		 h_addrtype = inet,
		 h_length = 4,
		 h_addr_list = [{127,0,0,28}]}} =
	inet:gethostbyname("resolve.otptest"),
    ok.

%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Get full DNS answer on nxdomain (when option set)
%% Check that we get the error code from the first server.

nxdomain_reply(Config) when is_list(Config) ->
    NS    = ns(Config),
    Name  = "nxdomain.otptest",
    Class = in,
    Type  = a,
    Opts  =
        [{nameservers,[NS]}, {servfail_retry_timeout, 1000}, verbose],
    ?P("try resolve"),
    {error, nxdomain} = inet_res:resolve(Name, Class, Type, Opts),
    {error, {nxdomain, Rec}} =
        inet_res:resolve(Name, Class, Type, [nxdomain_reply|Opts]),
    ?P("resolved: "
       "~n      ~p", [Rec]),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Answer just when timeout is triggered (OTP-9221).
last_ms_answer(Config) when is_list(Config) ->
    NS = ns(Config),
    Name = "ns.otptest",
    %%IP = {127,0,0,254},
    Time = inet_db:res_option(timeout) div inet_db:res_option(retry),
    PSpec = proxy_start(last_ms_answer, NS),
    ProxyNS = proxy_ns(PSpec),
    %%
    %% resolve; with short timeout to trigger Timeout =:= 0 in inet_res
    {error,timeout} =
	inet_res:resolve(
	  Name, in, a, [{nameservers,[ProxyNS]},verbose], Time + 10),
    %%
    proxy_wait(PSpec),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% First name server answers ?REFUSED, second does not answer.
%% Check that we get the error code from the first server.

intermediate_error(Config) when is_list(Config) ->
    NS      = ns(Config),
    Name    = "ns.otptest",
    Class   = in,
    Type    = a,
    IP      = {127,0,0,1},
    %% A "name server" that does not respond
    S       = ok(gen_udp:open(0, [{ip,IP},{active,false}])),
    Port    = ok(inet:port(S)),
    NSs     = [NS,{IP,Port}],
    Opts    = [{nameservers, NSs}, verbose],
    Timeout = 500,
    {error, {refused,_}} = inet_res:resolve(Name, Class, Type, Opts, Timeout),
    _ = gen_udp:close(S),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
%% is not received *too soon* (etd) answers noerror.

servfail_retry_timeout_default(Config) when is_list(Config) ->
    NS        = ns(Config),
    Name      = "ns.otptest",
    Class     = in,
    Type      = a,
    Opts      = [{nameservers,[NS]}, verbose],
    ?P("try resolve"),
    {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
    ?P("resolved: "
       "~n      ~p", [Rec]),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
%% is not received *too soon* (etd) answers noerror.

servfail_retry_timeout_1000(Config) when is_list(Config) ->
    NS        = ns(Config),
    Name      = "ns.otptest",
    Class     = in,
    Type      = a,
    Opts      = [{nameservers,[NS]}, {servfail_retry_timeout, 1000}, verbose],
    ?P("try resolve"),
    {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
    ?P("resolved: "
       "~n      ~p", [Rec]),
    ok.


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test that label encoding compression limits at 14 bits pointer size

label_compression_limit(Config) when is_list(Config) ->
    FirstSz = 4,
    Count = 512,
    Sz = 16,
    %% We create a long DNS message with an answer list containing
    %% 1+512+1 RR:s.  The first label is 4 chars that with message
    %% and RR overhead places the second label on offset 32.
    %% All other labels are 16 chars that with RR overhead
    %% places them on offsets of N * 32.
    %%
    %% The labels are: "ZZZZ", then; "AAAAAAAAAAAAAAAA",
    %% "AAAAAAAAAAAAAAAB", incrementing, so no one is
    %% equal and can not be compressed, until the last one
    %% that refers to the second to last one, so it could be compressed.
    %%
    %% However, the second to last label lands on offset 512 * 32 = 16384
    %% which is out of reach for compression since compression uses
    %% a 14 bit reference from the start of the message.
    %%
    %% The last label can only be compressed when we instead
    %% generate a message with *one less* char in the first label,
    %% placing the second to last label on offset 16383.
    %%
    %% So, MsgShort can use compression for the last RR
    %% by referring to the second to last RR, but MsgLong can not.
    %%
    %% Disclaimer:
    %%    All offsets and overheads are deduced
    %%    through trial and observation
    %%
    [D | Domains] = gen_domains(Count, lists:duplicate(Sz, $A), []),
    LastD = "Y." ++ D,
    DomainsCommon = lists:reverse(Domains, [D, LastD]),
    DomainsShort =  [lists:duplicate(FirstSz-1, $Z) | DomainsCommon],
    DomainsLong =   [lists:duplicate(FirstSz, $Z) | DomainsCommon],
    MsgShort = gen_msg(DomainsShort),
    MsgLong = gen_msg(DomainsLong),
    DataShort = inet_dns:encode(MsgShort),
    DataShortSz = byte_size(DataShort),
    ?P("DataShort[~w]:~n    ~p~n", [DataShortSz, DataShort]),
    DataLong = inet_dns:encode(MsgLong),
    DataLongSz = byte_size(DataLong),
    ?P("DataLong[~w]:~n    ~p~n", [DataLongSz, DataLong]),
    %% When the first (long) RR size pushes the last compressed label out,
    %% that occupied a 2 bytes reference, it instead becomes a label
    %% with 1 byte size and a final empty label size 1
    0 = DataLongSz - (DataShortSz+1 - 2 + 1+Sz+1),
    ok.

gen_msg(Domains) ->
    inet_dns:make_msg(
      [{header, inet_dns:make_header()},
       {anlist, gen_rrs(Domains)}]).

gen_rrs(Domains) ->
    [inet_dns:make_rr([{class,in},{type,a},{domain,D},{data,{17,18,19,20}}]) ||
        D <- Domains].

gen_domains(0, _Domain, Acc) ->
    Acc;
gen_domains(N, Domain, Acc) ->
    gen_domains(
      N - 1, incr_domain(Domain), [lists:reverse(Domain) | Acc]).

incr_domain([$Z | Domain]) ->
    [$A | incr_domain(Domain)];
incr_domain([Char | Domain]) ->
    [Char+1 | Domain].


%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compatibility tests. Call the inet_SUITE tests, but with
%% lookup = [file,dns] instead of [native]

gethostbyaddr() -> inet_SUITE:t_gethostbyaddr().
gethostbyaddr(Config) -> inet_SUITE:t_gethostbyaddr(Config).
gethostbyaddr_v6() -> inet_SUITE:t_gethostbyaddr_v6().
gethostbyaddr_v6(Config) -> inet_SUITE:t_gethostbyaddr_v6(Config).
gethostbyname() -> inet_SUITE:t_gethostbyname().
gethostbyname(Config) -> inet_SUITE:t_gethostbyname(Config).
gethostbyname_v6() -> inet_SUITE:t_gethostbyname_v6().
gethostbyname_v6(Config) -> inet_SUITE:t_gethostbyname_v6(Config).
getaddr() -> inet_SUITE:t_getaddr().
getaddr(Config) -> inet_SUITE:t_getaddr(Config).
getaddr_v6() -> inet_SUITE:t_getaddr_v6().
getaddr_v6(Config) -> inet_SUITE:t_getaddr_v6(Config).
ipv4_to_ipv6() -> inet_SUITE:ipv4_to_ipv6().
ipv4_to_ipv6(Config) -> inet_SUITE:ipv4_to_ipv6(Config).
host_and_addr() -> inet_SUITE:host_and_addr().
host_and_addr(Config) -> inet_SUITE:host_and_addr(Config).


timestamp() ->
    erlang:monotonic_time(milli_seconds).


%% Case flip helper, randomly flips the case of about every second [a-zA-Z]

-compile({inline, [caseflip/3]}).

caseflip([C | Cs]) when is_integer(C), $a =< C, C =< $z ->
    caseflip(Cs, C, $a - $A);
caseflip([C | Cs]) when is_integer(C), $A =< C, C =< $Z ->
    caseflip(Cs, C, $A - $a);
caseflip([C | Cs]) ->
    [C | caseflip(Cs)];
caseflip([]) ->
    [].
%%
caseflip(Cs, C, Diff) ->
    [case 0.5 =< rand:uniform() of
         true ->
             C - Diff;
         false ->
             C
     end | caseflip(Cs)].


tolower_email([$. | Cs]) ->
    [$. | tolower(Cs)];
tolower_email([C | Cs]) ->
    [C | tolower_email(Cs)].

%% Case fold according to RFC 4343
%%
tolower([C | Cs]) when is_integer(C) ->
    if  $A =< C, C =< $Z ->
	    [(C - $A + $a) | tolower(Cs)];
	true ->
	    [C | tolower(Cs)]
    end;
tolower([]) ->
    [].

toupper([C | Cs]) when is_integer(C) ->
    if  $a =< C, C =< $z ->
	    [(C - $a + $A) | toupper(Cs)];
	true ->
	    [C | toupper(Cs)]
    end;
toupper([]) ->
    [].

-compile({inline,[ok/1]}).
ok(ok) -> ok;
ok({ok,X}) -> X;
ok({error,Reason}) -> error(Reason).
