diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/kernel/src/inet_res.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/kernel/src/inet_res.erl')
-rw-r--r-- | lib/kernel/src/inet_res.erl | 846 |
1 files changed, 846 insertions, 0 deletions
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl new file mode 100644 index 0000000000..9b9e078898 --- /dev/null +++ b/lib/kernel/src/inet_res.erl @@ -0,0 +1,846 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% RFC 1035, 2671, 2782, 2915. +%% +-module(inet_res). + +%-compile(export_all). + +-export([gethostbyname/1, gethostbyname/2, gethostbyname/3, + gethostbyname_tm/3]). +-export([gethostbyaddr/1, gethostbyaddr/2, + gethostbyaddr_tm/2]). +-export([getbyname/2, getbyname/3, + getbyname_tm/3]). + +-export([resolve/3, resolve/4, resolve/5]). +-export([lookup/3, lookup/4, lookup/5]). +-export([dns_msg/1]). + +-export([nslookup/3, nslookup/4]). +-export([nnslookup/4, nnslookup/5]). + +-include_lib("kernel/include/inet.hrl"). +-include("inet_res.hrl"). +-include("inet_dns.hrl"). +-include("inet_int.hrl"). + +-define(verbose(Cond, Format, Args), + case begin Cond end of + true -> io:format(begin Format end, begin Args end); + false -> ok + end). + +%% -------------------------------------------------------------------------- +%% resolve: +%% +%% Nameserver query +%% + +resolve(Name, Class, Type) -> + resolve(Name, Class, Type, [], infinity). + +resolve(Name, Class, Type, Opts) -> + resolve(Name, Class, Type, Opts, infinity). + +resolve(Name, Class, Type, Opts, Timeout) -> + case nsdname(Name) of + {ok, Nm} -> + Timer = inet:start_timer(Timeout), + Res = res_query(Nm, Class, Type, Opts, Timer), + inet:stop_timer(Timer), + Res; + Error -> + Error + end. + +%% -------------------------------------------------------------------------- +%% lookup: +%% +%% Convenience wrapper to resolve/3,4,5 that filters out all answer data +%% fields of the class and type asked for. + +lookup(Name, Class, Type) -> + lookup(Name, Class, Type, []). + +lookup(Name, Class, Type, Opts) -> + lookup(Name, Class, Type, Opts, infinity). + +lookup(Name, Class, Type, Opts, Timeout) -> + lookup_filter(resolve(Name, Class, Type, Opts, Timeout), + Class, Type). + +lookup_filter({ok,#dns_rec{anlist=Answers}}, Class, Type) -> + [A#dns_rr.data || A <- Answers, + A#dns_rr.class =:= Class, + A#dns_rr.type =:= Type]; +lookup_filter({error,_}, _, _) -> []. + +%% -------------------------------------------------------------------------- +%% nslookup: +%% +%% Do a general nameserver lookup +%% +%% Perform nslookup on standard config !! +%% +%% To be deprecated + +nslookup(Name, Class, Type) -> + do_nslookup(Name, Class, Type, [], infinity). + +nslookup(Name, Class, Type, Timeout) when is_integer(Timeout), Timeout >= 0 -> + do_nslookup(Name, Class, Type, [], Timeout); +nslookup(Name, Class, Type, NSs) -> % For backwards compatibility + nnslookup(Name, Class, Type, NSs). % with OTP R6B only + +nnslookup(Name, Class, Type, NSs) -> + nnslookup(Name, Class, Type, NSs, infinity). + +nnslookup(Name, Class, Type, NSs, Timeout) -> + do_nslookup(Name, Class, Type, [{nameservers,NSs}], Timeout). + +do_nslookup(Name, Class, Type, Opts, Timeout) -> + case resolve(Name, Class, Type, Opts, Timeout) of + {error,{qfmterror,_}} -> {error,einval}; + {error,{Reason,_}} -> {error,Reason}; + Result -> Result + end. + +%% -------------------------------------------------------------------------- +%% options record +%% +-record(options, { % These must be sorted! + alt_nameservers,edns,inet6,nameservers,recurse, + retry,timeout,udp_payload_size,usevc, + verbose}). % this is a local option, not in inet_db +%% +%% Opts when is_list(Opts) -> #options{} +make_options(Opts0) -> + Opts = [if is_atom(Opt) -> + case atom_to_list(Opt) of + "no"++X -> {list_to_atom(X),false}; + _ -> {Opt,true} + end; + true -> Opt + end || Opt <- Opts0], + %% If the caller gives the nameservers option, the inet_db + %% alt_nameservers option should be regarded as empty, i.e + %% use only the nameservers the caller supplies. + SortedOpts = + lists:ukeysort(1, + case lists:keymember(nameservers, 1, Opts) of + true -> + case lists:keymember(alt_nameservers, 1, Opts) of + false -> + [{alt_nameservers,[]}|Opts]; + true -> + Opts + end; + false -> + Opts + end), + SortedNames = record_info(fields, options), + inet_db:res_update_conf(), + list_to_tuple([options|make_options(SortedOpts, SortedNames)]). + +make_options([_|_]=Opts0, []=Names0) -> + erlang:error(badarg, [Opts0,Names0]); +make_options([], []) -> []; +make_options([{verbose,Val}|Opts]=Opts0, [verbose|Names]=Names0) -> + if is_boolean(Val) -> + [Val|make_options(Opts, Names)]; + true -> + erlang:error(badarg, [Opts0,Names0]) + end; +make_options([{Opt,Val}|Opts]=Opts0, [Opt|Names]=Names0) -> + case inet_db:res_check_option(Opt, Val) of + true -> + [Val|make_options(Opts, Names)]; + false -> + erlang:error(badarg, [Opts0,Names0]) + end; +make_options(Opts, [verbose|Names]) -> + [false|make_options(Opts, Names)]; +make_options(Opts, [Name|Names]) -> + [inet_db:res_option(Name)|make_options(Opts, Names)]. + + +%% -------------------------------------------------------------------------- +%% +%% gethostbyaddr(ip_address()) => {ok, hostent()} | {error, Reason} +%% +%% where ip_address() is {A,B,C,D} ipv4 address +%% | {A,B,C,D,E,F,G,H} ipv6 address +%% | string versions of the above +%% | atom version +%% +%% -------------------------------------------------------------------------- + +gethostbyaddr(IP) -> gethostbyaddr_tm(IP,false). + +gethostbyaddr(IP,Timeout) -> + Timer = inet:start_timer(Timeout), + Res = gethostbyaddr_tm(IP,Timer), + inet:stop_timer(Timer), + Res. + +gethostbyaddr_tm({A,B,C,D} = IP, Timer) when ?ip(A,B,C,D) -> + inet_db:res_update_conf(), + case inet_db:gethostbyaddr(IP) of + {ok, HEnt} -> {ok, HEnt}; + _ -> res_gethostbyaddr(dn_in_addr_arpa(A,B,C,D), IP, Timer) + end; +%% ipv4 only ipv6 address +gethostbyaddr_tm({0,0,0,0,0,16#ffff,G,H},Timer) when is_integer(G+H) -> + gethostbyaddr_tm({G div 256, G rem 256, H div 256, H rem 256},Timer); +gethostbyaddr_tm({A,B,C,D,E,F,G,H} = IP, Timer) when ?ip6(A,B,C,D,E,F,G,H) -> + inet_db:res_update_conf(), + case inet_db:gethostbyaddr(IP) of + {ok, HEnt} -> {ok, HEnt}; + _ -> res_gethostbyaddr(dn_ip6_int(A,B,C,D,E,F,G,H), IP, Timer) + end; +gethostbyaddr_tm(Addr,Timer) when is_list(Addr) -> + case inet_parse:address(Addr) of + {ok, IP} -> gethostbyaddr_tm(IP,Timer); + _Error -> {error, formerr} + end; +gethostbyaddr_tm(Addr,Timer) when is_atom(Addr) -> + gethostbyaddr_tm(atom_to_list(Addr),Timer); +gethostbyaddr_tm(_,_) -> {error, formerr}. + +%% +%% Send the gethostbyaddr query to: +%% 1. the list of normal names servers +%% 2. the list of alternative name servers +%% +res_gethostbyaddr(Addr, IP, Timer) -> + case res_query(Addr, in, ptr, [], Timer) of + {ok, Rec} -> + inet_db:res_gethostbyaddr(IP, Rec); + {error,{qfmterror,_}} -> {error,einval}; + {error,{Reason,_}} -> {error,Reason}; + Error -> + Error + end. + +%% -------------------------------------------------------------------------- +%% +%% gethostbyname(domain_name()[,family [,Timer]) +%% => {ok, hostent()} | {error, Reason} +%% +%% where domain_name() is domain string or atom +%% +%% Caches the answer. +%% -------------------------------------------------------------------------- + +gethostbyname(Name) -> + case inet_db:res_option(inet6) of + true -> + gethostbyname_tm(Name, inet6, false); + false -> + gethostbyname_tm(Name, inet, false) + end. + +gethostbyname(Name,Family) -> + gethostbyname_tm(Name,Family,false). + +gethostbyname(Name,Family,Timeout) -> + Timer = inet:start_timer(Timeout), + Res = gethostbyname_tm(Name,Family,Timer), + inet:stop_timer(Timer), + Res. + +gethostbyname_tm(Name,inet,Timer) -> + getbyname_tm(Name,?S_A,Timer); +gethostbyname_tm(Name,inet6,Timer) -> + case getbyname_tm(Name,?S_AAAA,Timer) of + {ok,HEnt} -> {ok,HEnt}; + {error,nxdomain} -> + case getbyname_tm(Name, ?S_A,Timer) of + {ok, HEnt} -> + %% rewrite to a ipv4 only ipv6 address + {ok, + HEnt#hostent { + h_addrtype = inet6, + h_length = 16, + h_addr_list = + lists:map( + fun({A,B,C,D}) -> + {0,0,0,0,0,16#ffff,A*256+B,C*256+D} + end, HEnt#hostent.h_addr_list) + }}; + Error -> + Error + end; + Error -> + Error + end; +gethostbyname_tm(_Name, _Family, _Timer) -> + {error, einval}. + +%% -------------------------------------------------------------------------- +%% +%% getbyname(domain_name(), Type) => {ok, hostent()} | {error, Reason} +%% +%% where domain_name() is domain string or atom and Type is ?S_A, ?S_MX ... +%% +%% Caches the answer. +%% -------------------------------------------------------------------------- + +getbyname(Name, Type) -> + getbyname_tm(Name,Type,false). + +getbyname(Name, Type, Timeout) -> + Timer = inet:start_timer(Timeout), + Res = getbyname_tm(Name, Type, Timer), + inet:stop_timer(Timer), + Res. + +getbyname_tm(Name, Type, Timer) when is_list(Name) -> + case type_p(Type) of + true -> + case inet_parse:visible_string(Name) of + false -> {error, formerr}; + true -> + inet_db:res_update_conf(), + case inet_db:getbyname(Name, Type) of + {ok, HEnt} -> {ok, HEnt}; + _ -> res_getbyname(Name, Type, Timer) + end + end; + false -> + {error, formerr} + end; +getbyname_tm(Name,Type,Timer) when is_atom(Name) -> + getbyname_tm(atom_to_list(Name), Type,Timer); +getbyname_tm(_, _, _) -> {error, formerr}. + +type_p(Type) -> + lists:member(Type, [?S_A, ?S_AAAA, ?S_MX, ?S_NS, + ?S_MD, ?S_MF, ?S_CNAME, ?S_SOA, + ?S_MB, ?S_MG, ?S_MR, ?S_NULL, + ?S_WKS, ?S_HINFO, ?S_TXT, ?S_SRV, ?S_NAPTR, ?S_SPF, + ?S_UINFO, ?S_UID, ?S_GID]). + + + +%% This function and inet_db:getbyname/2 must look up names +%% in the same manner, but not from the same places. +%% +%% Assuming search path, i.e return value from inet_db:get_searchlist() +%% to be ["dom1", "dom2"]: +%% +%% Old behaviour (not this code but the previous version): +%% * For Name = "foo" +%% Name = "foo." try "foo.dom1", "foo.dom2" at normal nameservers +%% * For Name = "foo.bar" +%% Name = "foo.bar." try "foo.bar" at normal then alt. nameservers +%% then try "foo.bar.dom1", "foo.bar.dom2" +%% at normal nameservers +%% +%% New behaviour (this code), honoring the old behaviour but +%% doing better for absolute names: +%% * For Name = "foo" try "foo.dom1", "foo.dom2" at normal nameservers +%% * For Name = "foo.bar" try "foo.bar" at normal then alt. nameservers +%% then try "foo.bar.dom1", "foo.bar.dom2" +%% at normal nameservers +%% * For Name = "foo." try "foo" at normal then alt. nameservers +%% * For Name = "foo.bar." try "foo.bar" at normal then alt. nameservers +%% +%% +%% FIXME This is probably how it should be done: +%% Common behaviour (Solaris resolver) is: +%% * For Name = "foo." try "foo" +%% * For Name = "foo.bar." try "foo.bar" +%% * For Name = "foo" try "foo.dom1", "foo.dom2", "foo" +%% * For Name = "foo.bar" try "foo.bar.dom1", "foo.bar.dom2", "foo.bar" +%% That is to try Name as it is as a last resort if it is not absolute. +%% +res_getbyname(Name, Type, Timer) -> + {EmbeddedDots, TrailingDot} = inet_parse:dots(Name), + Dot = if TrailingDot -> ""; true -> "." end, + if TrailingDot -> + res_getby_query(Name, Type, Timer); + EmbeddedDots =:= 0 -> + res_getby_search(Name, Dot, + inet_db:get_searchlist(), + nxdomain, Type, Timer); + true -> + case res_getby_query(Name, Type, Timer) of + {error,_Reason}=Error -> + res_getby_search(Name, Dot, + inet_db:get_searchlist(), + Error, Type, Timer); + Other -> Other + end + end. + +res_getby_search(Name, Dot, [Dom | Ds], _Reason, Type, Timer) -> + case res_getby_query(Name++Dot++Dom, Type, Timer, + inet_db:res_option(nameservers)) of + {ok, HEnt} -> {ok, HEnt}; + {error, NewReason} -> + res_getby_search(Name, Dot, Ds, NewReason, Type, Timer) + end; +res_getby_search(_Name, _, [], Reason,_,_) -> + {error, Reason}. + +res_getby_query(Name, Type, Timer) -> + case res_query(Name, in, Type, [], Timer) of + {ok, Rec} -> + inet_db:res_hostent_by_domain(Name, Type, Rec); + {error,{qfmterror,_}} -> {error,einval}; + {error,{Reason,_}} -> {error,Reason}; + Error -> Error + end. + +res_getby_query(Name, Type, Timer, NSs) -> + case res_query(Name, in, Type, [], Timer, NSs) of + {ok, Rec} -> + inet_db:res_hostent_by_domain(Name, Type, Rec); + {error,{qfmterror,_}} -> {error,einval}; + {error,{Reason,_}} -> {error,Reason}; + Error -> Error + end. + + + +%% -------------------------------------------------------------------------- +%% query record +%% +-record(q, {options,edns,dns}). + + + +%% Query first nameservers list then alt_nameservers list +res_query(Name, Class, Type, Opts, Timer) -> + #q{options=#options{nameservers=NSs}}=Q = + make_query(Name, Class, Type, Opts), + case do_query(Q, NSs, Timer) of + {error,nxdomain}=Error -> + res_query_alt(Q, Error, Timer); + {error,{nxdomain,_}}=Error -> + res_query_alt(Q, Error, Timer); + {ok,#dns_rec{anlist=[]}}=Reply -> + res_query_alt(Q, Reply, Timer); + Reply -> Reply + end. + +%% Query just the argument nameservers list +res_query(Name, Class, Type, Opts, Timer, NSs) -> + Q = make_query(Name, Class, Type, Opts), + do_query(Q, NSs, Timer). + +res_query_alt(#q{options=#options{alt_nameservers=NSs}}=Q, Reply, Timer) -> + case NSs of + [] -> Reply; + _ -> + do_query(Q, NSs, Timer) + end. + +make_query(Dname, Class, Type, Opts) -> + Options = make_options(Opts), + case Options#options.edns of + false -> + #q{options=Options, + edns=undefined, + dns=make_query(Dname, Class, Type, Options, false)}; + Edns -> + #q{options=Options, + edns=make_query(Dname, Class, Type, Options, Edns), + dns=fun () -> + make_query(Dname, Class, Type, Options, false) + end} + end. + +%% XXX smarter would be to always construct both queries, +%% but make the EDNS query point into the DNS query binary. +%% It is only the header ARList length that need to be changed, +%% and the OPT record appended. +make_query(Dname, Class, Type, Options, Edns) -> + Id = inet_db:res_option(next_id), + Recurse = Options#options.recurse, + ARList = case Edns of + false -> []; + _ -> + PSz = Options#options.udp_payload_size, + [#dns_rr_opt{udp_payload_size=PSz, + version=Edns}] + end, + Msg = #dns_rec{header=#dns_header{id=Id, + opcode='query', + rd=Recurse, + rcode=?NOERROR}, + qdlist=[#dns_query{domain=Dname, + type=Type, + class=Class}], + arlist=ARList}, + ?verbose(Options#options.verbose, "Query: ~p~n", [dns_msg(Msg)]), + Buffer = inet_dns:encode(Msg), + {Id, Buffer}. + +%% -------------------------------------------------------------------------- +%% socket helpers +%% +-record(sock, {inet=undefined, inet6=undefined}). + +udp_open(#sock{inet6=I}=S, {A,B,C,D,E,F,G,H}) when ?ip6(A,B,C,D,E,F,G,H) -> + case I of + undefined -> + case gen_udp:open(0, [{active,false},binary,inet6]) of + {ok,J} -> + {ok,S#sock{inet6=J}}; + Error -> + Error + end; + _ -> + {ok,S} + end; +udp_open(#sock{inet=I}=S, {A,B,C,D}) when ?ip(A,B,C,D) -> + case I of + undefined -> + case gen_udp:open(0, [{active,false},binary,inet]) of + {ok,J} -> + {ok,S#sock{inet=J}}; + Error -> + Error + end; + _ -> + {ok,S} + end. + +udp_connect(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> + gen_udp:connect(I, IP, Port); +udp_connect(#sock{inet=I}, {A,B,C,D}=IP, Port) + when ?ip(A,B,C,D) -> + gen_udp:connect(I, IP, Port). + +udp_send(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port, Buffer) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> + gen_udp:send(I, IP, Port, Buffer); +udp_send(#sock{inet=I}, {A,B,C,D}=IP, Port, Buffer) + when ?ip(A,B,C,D), ?port(Port) -> + gen_udp:send(I, IP, Port, Buffer). + +udp_recv(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port, Timeout) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> + do_udp_recv(fun(T) -> gen_udp:recv(I, 0, T) end, IP, Port, Timeout); +udp_recv(#sock{inet=I}, {A,B,C,D}=IP, Port, Timeout) + when ?ip(A,B,C,D), ?port(Port) -> + do_udp_recv(fun(T) -> gen_udp:recv(I, 0, T) end, IP, Port, Timeout). + +do_udp_recv(Recv, IP, Port, Timeout) -> + do_udp_recv(Recv, IP, Port, Timeout, + if Timeout =/= 0 -> erlang:now(); true -> undefined end). + +do_udp_recv(Recv, IP, Port, Timeout, Then) -> + case Recv(Timeout) of + {ok,{IP,Port,Answer}} -> + {ok,Answer,erlang:max(0, Timeout - now_ms(erlang:now(), Then))}; + {ok,_} when Timeout =:= 0 -> + {error,timeout}; + {ok,_} -> + Now = erlang:now(), + T = erlang:max(0, Timeout - now_ms(Now, Then)), + do_udp_recv(Recv, IP, Port, T, Now); + Error -> Error + end. + +udp_close(#sock{inet=I,inet6=I6}) -> + if I =/= undefined -> gen_udp:close(I); true -> ok end, + if I6 =/= undefined -> gen_udp:close(I6); true -> ok end, + ok. + +%% +%% Send a query to the nameserver and return a reply +%% We first use socket server then we add the udp version +%% +%% Algorithm: (from manual page for dig) +%% for i = 0 to retry - 1 +%% for j = 1 to num_servers +%% send_query +%% wait((time * (2**i)) / num_servers) +%% end +%% end +%% + +do_query(_Q, [], _Timer) -> + {error,nxdomain}; +do_query(#q{options=#options{retry=Retry}}=Q, NSs, Timer) -> + query_retries(Q, NSs, Timer, Retry, 0, #sock{}). + +query_retries(_Q, _NSs, _Timer, Retry, Retry, S) -> + udp_close(S), + {error,timeout}; +query_retries(Q, NSs, Timer, Retry, I, S0) -> + Num = length(NSs), + if Num =:= 0 -> + {error,timeout}; + true -> + case query_nss(Q, NSs, Timer, Retry, I, S0, []) of + {S,{noanswer,ErrNSs}} -> %% remove unreachable nameservers + query_retries(Q, NSs--ErrNSs, Timer, Retry, I+1, S); + {S,Result} -> + udp_close(S), + Result + end + end. + +query_nss(_Q, [], _Timer, _Retry, _I, S, ErrNSs) -> + {S,{noanswer,ErrNSs}}; +query_nss(#q{edns=undefined}=Q, NSs, Timer, Retry, I, S, ErrNSs) -> + query_nss_dns(Q, NSs, Timer, Retry, I, S, ErrNSs); +query_nss(Q, NSs, Timer, Retry, I, S, ErrNSs) -> + query_nss_edns(Q, NSs, Timer, Retry, I, S, ErrNSs). + +query_nss_edns(#q{options=#options{udp_payload_size=PSz}=Options, + edns={Id,Buffer}}=Q, + [{IP,Port}=NS|NSs]=NSs0, Timer, Retry, I, S0, ErrNSs) -> + {S,Res}=Reply = query_ns(S0, Id, Buffer, IP, Port, Timer, + Retry, I, Options, PSz), + case Res of + timeout -> {S,{error,timeout}}; + {ok,_} -> Reply; + {error,{nxdomain,_}} -> Reply; + {error,{E,_}} when E =:= qfmterror; E =:= notimp; E =:= servfail; + E =:= badvers -> + query_nss_dns(Q, NSs0, Timer, Retry, I, S, ErrNSs); + {error,E} when E =:= fmt; E =:= enetunreach; E =:= econnrefused -> + query_nss(Q, NSs, Timer, Retry, I, S, [NS|ErrNSs]); + _Error -> + query_nss(Q, NSs, Timer, Retry, I, S, ErrNSs) + end. + +query_nss_dns(#q{dns=Qdns}=Q0, [{IP,Port}=NS|NSs], + Timer, Retry, I, S0, ErrNSs) -> + #q{options=Options,dns={Id,Buffer}}=Q = + if + is_function(Qdns, 0) -> Q0#q{dns=Qdns()}; + true -> Q0 + end, + {S,Res}=Reply = query_ns(S0, Id, Buffer, IP, Port, Timer, + Retry, I, Options, ?PACKETSZ), + case Res of + timeout -> {S,{error,timeout}}; + {ok,_} -> Reply; + {error,{E,_}} when E =:= nxdomain; E =:= qfmterror -> Reply; + {error,E} when E =:= fmt; E =:= enetunreach; E =:= econnrefused -> + query_nss(Q, NSs, Timer, Retry, I, S, [NS|ErrNSs]); + _Error -> + query_nss(Q, NSs, Timer, Retry, I, S, ErrNSs) + end. + +query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I, + #options{timeout=Tm,usevc=UseVC,verbose=Verbose}, + PSz) -> + case UseVC orelse iolist_size(Buffer) > PSz of + true -> + {S0,query_tcp(Tm, Id, Buffer, IP, Port, Timer, Verbose)}; + false -> + case udp_open(S0, IP) of + {ok,S} -> + {S,case query_udp(S, Id, Buffer, IP, Port, Timer, + Retry, I, Tm, Verbose) of + {ok,#dns_rec{header=H}} when H#dns_header.tc -> + query_tcp(Tm, Id, Buffer, + IP, Port, Timer, Verbose); + Reply -> Reply + end}; + Error -> + {S0,Error} + end + end. + +query_udp(S, Id, Buffer, IP, Port, Timer, Retry, I, Tm, Verbose) -> + Timeout = inet:timeout( (Tm * (1 bsl I)) div Retry, Timer), + ?verbose(Verbose, "Try UDP server : ~p:~p (timeout=~w)\n", + [IP, Port, Timeout]), + udp_connect(S, IP, Port), + udp_send(S, IP, Port, Buffer), + query_udp_recv(S, IP, Port, Id, Timeout, Verbose). + +query_udp_recv(S, IP, Port, Id, Timeout, Verbose) -> + case udp_recv(S, IP, Port, Timeout) of + {ok,Answer,T} -> + case decode_answer(Answer, Id, Verbose) of + {error, badid} -> + query_udp_recv(S, IP, Port, Id, T, Verbose); + Reply -> Reply + end; + {error, timeout} when Timeout =:= 0 -> + ?verbose(Verbose, "UDP server timeout\n", []), + timeout; + Error -> + ?verbose(Verbose, "UDP server error: ~p\n", [Error]), + Error + end. + +query_tcp(Tm, Id, Buffer, IP, Port, Timer, Verbose) -> + Timeout = inet:timeout(Tm*5, Timer), + ?verbose(Verbose, "Try TCP server : ~p:~p (timeout=~w)\n", + [IP, Port, Timeout]), + Family = case IP of + {A,B,C,D} when ?ip(A,B,C,D) -> inet; + {A,B,C,D,E,F,G,H} when ?ip6(A,B,C,D,E,F,G,H) -> inet6 + end, + try gen_tcp:connect(IP, Port, + [{active,false},{packet,2},binary,Family], + Timeout) of + {ok, S} -> + gen_tcp:send(S, Buffer), + case gen_tcp:recv(S, 0, Timeout) of + {ok, Answer} -> + gen_tcp:close(S), + case decode_answer(Answer, Id, Verbose) of + {ok, _} = OK -> OK; + {error, badid} -> {error, servfail}; + Error -> Error + end; + Error -> + gen_tcp:close(S), + case Error of + {error, timeout} when Timeout =:= 0 -> + ?verbose(Verbose, "TCP server recv timeout\n", []), + timeout; + _ -> + ?verbose(Verbose, "TCP server recv error: ~p\n", + [Error]), + Error + end + end; + {error, timeout} when Timeout =:= 0 -> + ?verbose(Verbose, "TCP server connect timeout\n", []), + timeout; + Error -> + ?verbose(Verbose, "TCP server error: ~p\n", [Error]), + Error + catch + _:_ -> {error, einval} + end. + +decode_answer(Answer, Id, Verbose) -> + case inet_dns:decode(Answer) of + {ok, Msg} -> + ?verbose(Verbose, "Got reply: ~p~n", [dns_msg(Msg)]), + E = case lists:keyfind(dns_rr_opt, 1, Msg#dns_rec.arlist) of + false -> 0; + #dns_rr_opt{ext_rcode=ExtRCode} -> ExtRCode + end, + H = Msg#dns_rec.header, + RCode = (E bsl 4) bor H#dns_header.rcode, + case RCode of + ?NOERROR -> + if H#dns_header.id =/= Id -> + {error,badid}; + length(Msg#dns_rec.qdlist) =/= 1 -> + {error,{noquery,Msg}}; + true -> + {ok, Msg} + end; + ?FORMERR -> {error,{qfmterror,Msg}}; + ?SERVFAIL -> {error,{servfail,Msg}}; + ?NXDOMAIN -> {error,{nxdomain,Msg}}; + ?NOTIMP -> {error,{notimp,Msg}}; + ?REFUSED -> {error,{refused,Msg}}; + ?BADVERS -> {error,{badvers,Msg}}; + _ -> {error,{unknown,Msg}} + end; + Error -> + ?verbose(Verbose, "Got reply: ~p~n", [Error]), + Error + end. + +%% +%% Transform domain name or address +%% 1. "a.b.c" => +%% "a.b.c" +%% 2. "1.2.3.4" => +%% "4.3.2.1.IN-ADDR.ARPA" +%% 3. "4321:0:1:2:3:4:567:89ab" => +%% "b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.1.2.3.4.IP6.ARPA" +%% 4. {1,2,3,4} => as 2. +%% 5. {1,2,3,4,5,6,7,8} => as 3. +%% +nsdname({A,B,C,D}) -> + {ok, dn_in_addr_arpa(A,B,C,D)}; +nsdname({A,B,C,D,E,F,G,H}) -> + {ok, dn_ip6_int(A,B,C,D,E,F,G,H)}; +nsdname(Name) when is_list(Name) -> + case inet_parse:visible_string(Name) of + true -> + case inet_parse:address(Name) of + {ok, Addr} -> + nsdname(Addr); + _ -> + {ok, Name} + end; + _ -> {error, formerr} + end; +nsdname(Name) when is_atom(Name) -> + nsdname(atom_to_list(Name)); +nsdname(_) -> {error, formerr}. + +dn_in_addr_arpa(A,B,C,D) -> + integer_to_list(D) ++ + ("." ++ integer_to_list(C) ++ + ("." ++ integer_to_list(B) ++ + ("." ++ integer_to_list(A) ++ ".IN-ADDR.ARPA"))). + +dn_ip6_int(A,B,C,D,E,F,G,H) -> + dnib(H) ++ + (dnib(G) ++ + (dnib(F) ++ + (dnib(E) ++ + (dnib(D) ++ + (dnib(C) ++ + (dnib(B) ++ + (dnib(A) ++ "IP6.ARPA"))))))). + + + +-compile({inline, [dnib/1, dnib/3]}). +dnib(X) -> + L = erlang:integer_to_list(X, 16), + dnib(4-length(L), L, []). +%% +dnib(0, [], Acc) -> Acc; +dnib(0, [C|Cs], Acc) -> + dnib(0, Cs, [C,$.|Acc]); +dnib(N, Cs, Acc) -> + dnib(N-1, Cs, [$0,$.|Acc]). + + + +dns_msg([]) -> []; +dns_msg([{Field,Msg}|Fields]) -> + [{Field,dns_msg(Msg)}|dns_msg(Fields)]; +dns_msg([Msg|Msgs]) -> + [dns_msg(Msg)|dns_msg(Msgs)]; +dns_msg(Msg) -> + case inet_dns:record_type(Msg) of + undefined -> Msg; + Type -> + Fields = inet_dns:Type(Msg), + {Type,dns_msg(Fields)} + end. + +-compile({inline, [now_ms/2]}). +now_ms({Meg1,Sec1,Mic1}, {Meg0,Sec0,Mic0}) -> + ((Meg1-Meg0)*1000000 + (Sec1-Sec0))*1000 + ((Mic1-Mic0) div 1000). |