aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/inet_db.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/kernel/src/inet_db.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/kernel/src/inet_db.erl')
-rw-r--r--lib/kernel/src/inet_db.erl1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
new file mode 100644
index 0000000000..211847014f
--- /dev/null
+++ b/lib/kernel/src/inet_db.erl
@@ -0,0 +1,1525 @@
+%%
+%% %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%
+%%
+
+-module(inet_db).
+
+%% Store info about ip addresses, names, aliases host files resolver
+%% options
+
+%% If the macro DEBUG is defined during compilation,
+%% debug printouts are done through erlang:display/1.
+%% Activate this feature by starting the compiler
+%% with> erlc -DDEBUG ...
+%% or by> setenv ERL_COMPILER_FLAGS DEBUG
+%% before running make (in the OTP make system)
+%% (the example is for tcsh)
+
+%% External exports
+-export([start/0, start_link/0, stop/0, reset/0, clear_cache/0]).
+-export([add_rr/1,add_rr/5,del_rr/4]).
+-export([add_ns/1,add_ns/2, ins_ns/1, ins_ns/2,
+ del_ns/2, del_ns/1, del_ns/0]).
+-export([add_alt_ns/1,add_alt_ns/2, ins_alt_ns/1, ins_alt_ns/2,
+ del_alt_ns/2, del_alt_ns/1, del_alt_ns/0]).
+-export([add_search/1,ins_search/1,del_search/1, del_search/0]).
+-export([set_lookup/1, set_recurse/1]).
+-export([set_socks_server/1, set_socks_port/1, add_socks_methods/1,
+ del_socks_methods/1, del_socks_methods/0,
+ add_socks_noproxy/1, del_socks_noproxy/1]).
+-export([set_cache_size/1, set_cache_refresh/1]).
+-export([set_timeout/1, set_retry/1, set_inet6/1, set_usevc/1]).
+-export([set_edns/1, set_udp_payload_size/1]).
+-export([set_resolv_conf/1, set_hosts_file/1, get_hosts_file/0]).
+-export([tcp_module/0, set_tcp_module/1]).
+-export([udp_module/0, set_udp_module/1]).
+-export([sctp_module/0,set_sctp_module/1]).
+-export([register_socket/2, unregister_socket/1, lookup_socket/1]).
+
+%% Host name & domain
+-export([set_hostname/1, set_domain/1]).
+-export([gethostname/0]).
+
+%% file interface
+-export([add_host/2, del_host/1, clear_hosts/0, add_hosts/1]).
+-export([add_resolv/1]).
+-export([add_rc/1, add_rc_bin/1, add_rc_list/1, get_rc/0]).
+
+-export([res_option/1, res_option/2, res_check_option/2]).
+-export([socks_option/1]).
+-export([getbyname/2, get_searchlist/0]).
+-export([gethostbyaddr/1]).
+-export([res_gethostbyaddr/2,res_hostent_by_domain/3]).
+-export([res_update_conf/0, res_update_hosts/0]).
+%% inet help functions
+-export([tolower/1]).
+-ifdef(DEBUG).
+-define(dbg(Fmt, Args), io:format(Fmt, Args)).
+-else.
+-define(dbg(Fmd, Args), ok).
+-endif.
+
+-include_lib("kernel/include/file.hrl").
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+
+-record(state,
+ {db, %% resolver data
+ cache, %% bag of resource records
+ hosts_byname, %% hosts table
+ hosts_byaddr, %% hosts table
+ hosts_file_byname, %% hosts table from system file
+ hosts_file_byaddr, %% hosts table from system file
+ cache_timer %% timer reference for refresh
+ }).
+
+-include("inet.hrl").
+-include("inet_int.hrl").
+-include("inet_res.hrl").
+-include("inet_dns.hrl").
+-include("inet_config.hrl").
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+
+start() ->
+ case gen_server:start({local, inet_db}, inet_db, [], []) of
+ {ok,Pid} -> inet_config:init(), {ok,Pid};
+ Error -> Error
+ end.
+
+
+start_link() ->
+ case gen_server:start_link({local, inet_db}, inet_db, [], []) of
+ {ok,Pid} -> inet_config:init(), {ok,Pid};
+ Error -> Error
+ end.
+
+call(Req) ->
+ gen_server:call(inet_db, Req, infinity).
+
+stop() ->
+ call(stop).
+
+reset() ->
+ call(reset).
+
+
+%% insert all resolve options from this file (MAY GO)
+add_resolv(File) ->
+ case inet_parse:resolv(File) of
+ {ok, Res} -> add_rc_list(Res);
+ Error -> Error
+ end.
+
+%% add all aliases from this hosts file (MAY GO)
+add_hosts(File) ->
+ case inet_parse:hosts(File) of
+ {ok, Res} ->
+ lists:foreach(
+ fun({IP, Name, Aliases}) -> add_host(IP, [Name|Aliases]) end,
+ Res);
+ Error -> Error
+ end.
+
+
+add_host(IP, Names) -> call({add_host, IP, Names}).
+
+del_host(IP) -> call({del_host, IP}).
+
+clear_hosts() -> call(clear_hosts).
+
+%% add to the end of name server list
+add_ns(IP) ->
+ add_ns(IP,?NAMESERVER_PORT).
+add_ns(IP,Port) ->
+ call({listop, nameservers, add, {IP,Port}}).
+
+%% insert at head of name server list
+ins_ns(IP) ->
+ ins_ns(IP, ?NAMESERVER_PORT).
+ins_ns(IP,Port) ->
+ call({listop, nameservers, ins, {IP,Port}}).
+
+%% delete this name server entry (delete all ns having this ip)
+del_ns(IP) ->
+ del_ns(IP, ?NAMESERVER_PORT).
+del_ns(IP, Port) ->
+ call({listop, nameservers, del, {IP,Port}}).
+
+del_ns() ->
+ call({listdel, nameservers}).
+
+%% ALTERNATIVE NAME SERVER
+%% add to the end of name server list
+add_alt_ns(IP) ->
+ add_alt_ns(IP, ?NAMESERVER_PORT).
+add_alt_ns(IP,Port) ->
+ call({listop, alt_nameservers, add, {IP,Port}}).
+
+%% insert at head of name server list
+ins_alt_ns(IP) ->
+ ins_alt_ns(IP, ?NAMESERVER_PORT).
+ins_alt_ns(IP,Port) ->
+ call({listop, alt_nameservers, ins, {IP,Port}}).
+
+%% delete this name server entry
+del_alt_ns(IP) ->
+ del_alt_ns(IP, ?NAMESERVER_PORT).
+del_alt_ns(IP, Port) ->
+ call({listop, alt_nameservers, del, {IP,Port}}).
+
+del_alt_ns() ->
+ call({listdel, alt_nameservers}).
+
+%% add this domain to the search list
+add_search(Domain) when is_list(Domain) ->
+ call({listop, search, add, Domain}).
+
+ins_search(Domain) when is_list(Domain) ->
+ call({listop, search, ins, Domain}).
+
+del_search(Domain) ->
+ call({listop, search, del, Domain}).
+
+del_search() ->
+ call({listdel, search}).
+
+%% set host name used by inet
+%% Should only be used by inet_config at startup!
+set_hostname(Name) ->
+ call({set_hostname, Name}).
+
+%% set default domain
+set_domain(Domain) -> res_option(domain, Domain).
+
+%% set lookup methods
+set_lookup(Methods) -> res_option(lookup, Methods).
+
+%% resolver
+set_recurse(Flag) -> res_option(recurse, Flag).
+
+set_timeout(Time) -> res_option(timeout, Time).
+
+set_retry(N) -> res_option(retry, N).
+
+set_inet6(Bool) -> res_option(inet6, Bool).
+
+set_usevc(Bool) -> res_option(usevc, Bool).
+
+set_edns(Version) -> res_option(edns, Version).
+
+set_udp_payload_size(Size) -> res_option(udp_payload_size, Size).
+
+set_resolv_conf(Fname) -> res_option(resolv_conf, Fname).
+
+set_hosts_file(Fname) -> res_option(hosts_file, Fname).
+
+get_hosts_file() ->
+ get_rc_hosts([], [], inet_hosts_file_byname).
+
+%% set socks options
+set_socks_server(Server) -> call({set_socks_server, Server}).
+
+set_socks_port(Port) -> call({set_socks_port, Port}).
+
+add_socks_methods(Ms) -> call({add_socks_methods,Ms}).
+
+del_socks_methods(Ms) -> call({del_socks_methods,Ms}).
+
+del_socks_methods() -> call(del_socks_methods).
+
+add_socks_noproxy({Net,Mask}) -> call({add_socks_noproxy, {Net,Mask}}).
+
+del_socks_noproxy(Net) -> call({del_socks_noproxy, Net}).
+
+%% cache options
+set_cache_size(Limit) -> call({set_cache_size, Limit}).
+
+set_cache_refresh(Time) -> call({set_cache_refresh, Time}).
+
+clear_cache() -> call(clear_cache).
+
+
+set_tcp_module(Module) -> call({set_tcp_module, Module}).
+
+tcp_module() -> db_get(tcp_module).
+
+set_udp_module(Module) -> call({set_udp_module, Module}).
+
+udp_module() -> db_get(udp_module).
+
+set_sctp_module(Family)-> call({set_sctp_module,Family}).
+
+sctp_module()-> db_get(sctp_module).
+
+%% Add an inetrc file
+add_rc(File) ->
+ case file:consult(File) of
+ {ok, List} -> add_rc_list(List);
+ Error -> Error
+ end.
+
+%% Add an inetrc binary term must be a rc list
+add_rc_bin(Bin) ->
+ case catch binary_to_term(Bin) of
+ List when is_list(List) ->
+ add_rc_list(List);
+ _ ->
+ {error, badarg}
+ end.
+
+add_rc_list(List) -> call({add_rc_list, List}).
+
+
+
+%% All kind of flavors !
+translate_lookup(["bind" | Ls]) -> [dns | translate_lookup(Ls)];
+translate_lookup(["dns" | Ls]) -> [dns | translate_lookup(Ls)];
+translate_lookup(["hosts" | Ls]) -> [file | translate_lookup(Ls)];
+translate_lookup(["files" | Ls]) -> [file | translate_lookup(Ls)];
+translate_lookup(["file" | Ls]) -> [file | translate_lookup(Ls)];
+translate_lookup(["yp" | Ls]) -> [yp | translate_lookup(Ls)];
+translate_lookup(["nis" | Ls]) -> [nis | translate_lookup(Ls)];
+translate_lookup(["nisplus" | Ls]) -> [nisplus | translate_lookup(Ls)];
+translate_lookup(["native" | Ls]) -> [native | translate_lookup(Ls)];
+translate_lookup([M | Ls]) when is_atom(M) -> translate_lookup([atom_to_list(M) | Ls]);
+translate_lookup([_ | Ls]) -> translate_lookup(Ls);
+translate_lookup([]) -> [].
+
+valid_lookup() -> [dns, file, yp, nis, nisplus, native].
+
+
+%% Reconstruct an inetrc sturcture from inet_db
+get_rc() ->
+ get_rc([hosts, domain, nameservers, search, alt_nameservers,
+ timeout, retry, inet6, usevc,
+ edns, udp_payload_size, resolv_conf, hosts_file,
+ socks5_server, socks5_port, socks5_methods, socks5_noproxy,
+ udp, sctp, tcp, host, cache_size, cache_refresh, lookup], []).
+
+get_rc([K | Ks], Ls) ->
+ case K of
+ hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byname);
+ domain -> get_rc(domain, res_domain, "", Ks, Ls);
+ nameservers -> get_rc_ns(db_get(res_ns),nameservers,Ks,Ls);
+ alt_nameservers -> get_rc_ns(db_get(res_alt_ns),alt_nameservers,Ks,Ls);
+ search -> get_rc(search, res_search, [], Ks, Ls);
+ timeout -> get_rc(timeout,res_timeout,?RES_TIMEOUT, Ks,Ls);
+ retry -> get_rc(retry, res_retry, ?RES_RETRY, Ks, Ls);
+ inet6 -> get_rc(inet6, res_inet6, false, Ks, Ls);
+ usevc -> get_rc(usevc, res_usevc, false, Ks, Ls);
+ edns -> get_rc(edns, res_edns, false, Ks, Ls);
+ udp_payload_size -> get_rc(udp_payload_size, res_udp_payload_size,
+ ?DNS_UDP_PAYLOAD_SIZE, Ks, Ls);
+ resolv_conf -> get_rc(resolv_conf, res_resolv_conf, undefined, Ks, Ls);
+ hosts_file -> get_rc(hosts_file, res_hosts_file, undefined, Ks, Ls);
+ tcp -> get_rc(tcp, tcp_module, ?DEFAULT_TCP_MODULE, Ks, Ls);
+ udp -> get_rc(udp, udp_module, ?DEFAULT_UDP_MODULE, Ks, Ls);
+ sctp -> get_rc(sctp, sctp_module, ?DEFAULT_SCTP_MODULE, Ks, Ls);
+ lookup -> get_rc(lookup, res_lookup, [native,file], Ks, Ls);
+ cache_size -> get_rc(cache_size, cache_size, ?CACHE_LIMIT, Ks, Ls);
+ cache_refresh ->
+ get_rc(cache_refresh, cache_refresh_interval,?CACHE_REFRESH,Ks,Ls);
+ socks5_server -> get_rc(socks5_server, socks5_server, "", Ks, Ls);
+ socks5_port -> get_rc(socks5_port,socks5_port,?IPPORT_SOCKS,Ks,Ls);
+ socks5_methods -> get_rc(socks5_methods,socks5_methods,[none],Ks,Ls);
+ socks5_noproxy ->
+ case db_get(socks5_noproxy) of
+ [] -> get_rc(Ks, Ls);
+ NoProxy -> get_rc_noproxy(NoProxy, Ks, Ls)
+ end;
+ _ ->
+ get_rc(Ks, Ls)
+ end;
+get_rc([], Ls) ->
+ lists:reverse(Ls).
+
+get_rc(Name, Key, Default, Ks, Ls) ->
+ case db_get(Key) of
+ Default -> get_rc(Ks, Ls);
+ Value -> get_rc(Ks, [{Name, Value} | Ls])
+ end.
+
+get_rc_noproxy([{Net,Mask} | Ms], Ks, Ls) ->
+ get_rc_noproxy(Ms, Ks, [{socks5_noproxy, Net, Mask} | Ls]);
+get_rc_noproxy([], Ks, Ls) -> get_rc(Ks, Ls).
+
+get_rc_ns([{IP,?NAMESERVER_PORT} | Ns], Tag, Ks, Ls) ->
+ get_rc_ns(Ns, Tag, Ks, [{Tag, IP} | Ls]);
+get_rc_ns([{IP,Port} | Ns], Tag, Ks, Ls) ->
+ get_rc_ns(Ns, Tag, Ks, [{Tag, IP, Port} | Ls]);
+get_rc_ns([], _Tag, Ks, Ls) ->
+ get_rc(Ks, Ls).
+
+get_rc_hosts(Ks, Ls, Tab) ->
+ case lists:keysort(3, ets:tab2list(Tab)) of
+ [] -> get_rc(Ks, Ls);
+ [{N,_,IP}|Hosts] -> get_rc_hosts(Ks, Ls, IP, Hosts, [N])
+ end.
+
+get_rc_hosts(Ks, Ls, IP, [], Ns) ->
+ get_rc(Ks, [{host,IP,lists:reverse(Ns)}|Ls]);
+get_rc_hosts(Ks, Ls, IP, [{N,_,IP}|Hosts], Ns) ->
+ get_rc_hosts(Ks, Ls, IP, Hosts, [N|Ns]);
+get_rc_hosts(Ks, Ls, IP, [{N,_,NewIP}|Hosts], Ns) ->
+ [{host,IP,lists:reverse(Ns)}|get_rc_hosts(Ks, Ls, NewIP, Hosts, [N])].
+
+%%
+%% Resolver options
+%%
+res_option(next_id) ->
+ Cnt = ets:update_counter(inet_db, res_id, 1),
+ case Cnt band 16#ffff of
+ 0 ->
+ ets:update_counter(inet_db, res_id, -Cnt),
+ 0;
+ Id ->
+ Id
+ end;
+res_option(Option) ->
+ case res_optname(Option) of
+ undefined ->
+ erlang:error(badarg, [Option]);
+ ResOptname ->
+ db_get(ResOptname)
+ end.
+
+res_option(Option, Value) ->
+ case res_optname(Option) of
+ undefined ->
+ erlang:error(badarg, [Option,Value]);
+ _ ->
+ call({res_set,Option,Value})
+ end.
+
+res_optname(nameserver) -> res_ns; %% Legacy
+res_optname(alt_nameserver) -> res_alt_ns; %% Legacy
+res_optname(nameservers) -> res_ns;
+res_optname(alt_nameservers) -> res_alt_ns;
+res_optname(domain) -> res_domain;
+res_optname(lookup) -> res_lookup;
+res_optname(recurse) -> res_recurse;
+res_optname(search) -> res_search;
+res_optname(retry) -> res_retry;
+res_optname(timeout) -> res_timeout;
+res_optname(inet6) -> res_inet6;
+res_optname(usevc) -> res_usevc;
+res_optname(edns) -> res_edns;
+res_optname(udp_payload_size) -> res_udp_payload_size;
+res_optname(resolv_conf) -> res_resolv_conf;
+res_optname(hosts_file) -> res_hosts_file;
+res_optname(_) -> undefined.
+
+res_check_option(nameserver, NSs) -> %% Legacy
+ res_check_list(NSs, fun res_check_ns/1);
+res_check_option(alt_nameserver, NSs) -> %% Legacy
+ res_check_list(NSs, fun res_check_ns/1);
+res_check_option(nameservers, NSs) ->
+ res_check_list(NSs, fun res_check_ns/1);
+res_check_option(alt_nameservers, NSs) ->
+ res_check_list(NSs, fun res_check_ns/1);
+res_check_option(domain, Dom) ->
+ inet_parse:visible_string(Dom);
+res_check_option(lookup, Methods) ->
+ try lists_subtract(Methods, valid_lookup()) of
+ [] -> true;
+ _ -> false
+ catch
+ error:_ -> false
+ end;
+res_check_option(recurse, R) when R =:= 0; R =:= 1 -> true; %% Legacy
+res_check_option(recurse, R) when is_boolean(R) -> true;
+res_check_option(search, SearchList) ->
+ res_check_list(SearchList, fun res_check_search/1);
+res_check_option(retry, N) when is_integer(N), N > 0 -> true;
+res_check_option(timeout, T) when is_integer(T), T > 0 -> true;
+res_check_option(inet6, Bool) when is_boolean(Bool) -> true;
+res_check_option(usevc, Bool) when is_boolean(Bool) -> true;
+res_check_option(edns, V) when V =:= false; V =:= 0 -> true;
+res_check_option(udp_payload_size, S) when is_integer(S), S >= 512 -> true;
+res_check_option(resolv_conf, "") -> true;
+res_check_option(resolv_conf, F) ->
+ res_check_option_absfile(F);
+res_check_option(hosts_file, "") -> true;
+res_check_option(hosts_file, F) ->
+ res_check_option_absfile(F);
+res_check_option(_, _) -> false.
+
+res_check_option_absfile(F) ->
+ try filename:pathtype(F) of
+ absolute -> true;
+ _ -> false
+ catch
+ _:_ -> false
+ end.
+
+res_check_list([], _Fun) -> true;
+res_check_list([H|T], Fun) ->
+ case Fun(H) of
+ true -> res_check_list(T, Fun);
+ false -> false
+ end;
+res_check_list(_, _Fun) -> false.
+
+res_check_ns({{A,B,C,D,E,F,G,H}, Port})
+ when ?ip6(A,B,C,D,E,F,G,H), Port band 65535 =:= Port -> true;
+res_check_ns({{A,B,C,D}, Port})
+ when ?ip(A,B,C,D), Port band 65535 =:= Port -> true;
+res_check_ns(_) -> false.
+
+res_check_search("") -> true;
+res_check_search(Dom) -> inet_parse:visible_string(Dom).
+
+socks_option(server) -> db_get(socks5_server);
+socks_option(port) -> db_get(socks5_port);
+socks_option(methods) -> db_get(socks5_methods);
+socks_option(noproxy) -> db_get(socks5_noproxy).
+
+gethostname() -> db_get(hostname).
+
+res_update_conf() ->
+ res_update(res_resolv_conf, res_resolv_conf_tm, res_resolv_conf_info,
+ set_resolv_conf_tm, fun set_resolv_conf/1).
+
+res_update_hosts() ->
+ res_update(res_hosts_file, res_hosts_file_tm, res_hosts_file_info,
+ set_hosts_file_tm, fun set_hosts_file/1).
+
+res_update(Tag, TagTm, TagInfo, CallTag, SetFun) ->
+ case db_get(TagTm) of
+ undefined -> ok;
+ TM ->
+ case times() of
+ Now when Now >= TM + ?RES_FILE_UPDATE_TM ->
+ case db_get(Tag) of
+ undefined ->
+ SetFun("");
+ "" ->
+ SetFun("");
+ File ->
+ case erl_prim_loader:read_file_info(File) of
+ {ok, Finfo0} ->
+ Finfo =
+ Finfo0#file_info{access = undefined,
+ atime = undefined},
+ case db_get(TagInfo) of
+ Finfo ->
+ call({CallTag, Now});
+ _ ->
+ SetFun(File)
+ end;
+ _ ->
+ call({CallTag, Now}),
+ error
+ end
+ end;
+ _ -> ok
+ end
+ end.
+
+db_get(Name) ->
+ case ets:lookup(inet_db, Name) of
+ [] -> undefined;
+ [{_,Val}] -> Val
+ end.
+
+add_rr(RR) ->
+ call({add_rr, RR}).
+
+add_rr(Domain, Class, Type, TTL, Data) ->
+ call({add_rr, #dns_rr { domain = Domain, class = Class,
+ type = Type, ttl = TTL, data = Data}}).
+
+del_rr(Domain, Class, Type, Data) ->
+ call({del_rr, #dns_rr { domain = Domain, class = Class,
+ type = Type, cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_', data = Data}}).
+
+res_cache_answer(Rec) ->
+ lists:foreach( fun(RR) -> add_rr(RR) end, Rec#dns_rec.anlist).
+
+
+
+
+%%
+%% getbyname (cache version)
+%%
+%% This function and inet_res:res_getbyname/3 must look up names
+%% in the same manner, but not from the same places.
+%%
+getbyname(Name, Type) ->
+ {EmbeddedDots, TrailingDot} = inet_parse:dots(Name),
+ Dot = if TrailingDot -> ""; true -> "." end,
+ if TrailingDot ->
+ hostent_by_domain(Name, Type);
+ EmbeddedDots =:= 0 ->
+ getbysearch(Name, Dot, get_searchlist(), Type, {error,nxdomain});
+ true ->
+ case hostent_by_domain(Name, Type) of
+ {error,_}=Error ->
+ getbysearch(Name, Dot, get_searchlist(), Type, Error);
+ Other -> Other
+ end
+ end.
+
+getbysearch(Name, Dot, [Dom | Ds], Type, _) ->
+ case hostent_by_domain(Name ++ Dot ++ Dom, Type) of
+ {ok, HEnt} -> {ok, HEnt};
+ Error ->
+ getbysearch(Name, Dot, Ds, Type, Error)
+ end;
+getbysearch(_Name, _Dot, [], _Type, Error) ->
+ Error.
+
+
+
+%%
+%% get_searchlist
+%%
+get_searchlist() ->
+ case res_option(search) of
+ [] -> [res_option(domain)];
+ L -> L
+ end.
+
+
+
+make_hostent(Name, Addrs, Aliases, ?S_A) ->
+ #hostent {
+ h_name = Name,
+ h_addrtype = inet,
+ h_length = 4,
+ h_addr_list = Addrs,
+ h_aliases = Aliases
+ };
+make_hostent(Name, Addrs, Aliases, ?S_AAAA) ->
+ #hostent {
+ h_name = Name,
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = Addrs,
+ h_aliases = Aliases
+ };
+make_hostent(Name, Datas, Aliases, Type) ->
+ %% Use #hostent{} for other Types as well !
+ #hostent {
+ h_name = Name,
+ h_addrtype = Type,
+ h_length = length(Datas),
+ h_addr_list = Datas,
+ h_aliases = Aliases
+ }.
+
+hostent_by_domain(Domain, Type) ->
+ ?dbg("hostent_by_domain: ~p~n", [Domain]),
+ hostent_by_domain(stripdot(Domain), [], Type).
+
+hostent_by_domain(Domain, Aliases, Type) ->
+ case lookup_type(Domain, Type) of
+ [] ->
+ case lookup_cname(Domain) of
+ [] ->
+ {error, nxdomain};
+ [CName | _] ->
+ case lists:member(CName, [Domain | Aliases]) of
+ true ->
+ {error, nxdomain};
+ false ->
+ hostent_by_domain(CName, [Domain | Aliases], Type)
+ end
+ end;
+ Addrs ->
+ {ok, make_hostent(Domain, Addrs, Aliases, Type)}
+ end.
+
+%% lookup address record
+lookup_type(Domain, Type) ->
+ [R#dns_rr.data || R <- lookup_rr(Domain, in, Type) ].
+
+%% lookup canonical name
+lookup_cname(Domain) ->
+ [R#dns_rr.data || R <- lookup_rr(Domain, in, ?S_CNAME) ].
+
+%% Have to do all lookups (changes to the db) in the
+%% process in order to make it possible to refresh the cache.
+lookup_rr(Domain, Class, Type) ->
+ call({lookup_rr, Domain, Class, Type}).
+
+%%
+%% hostent_by_domain (newly resolved version)
+%% match data field directly and cache RRs.
+%%
+res_hostent_by_domain(Domain, Type, Rec) ->
+ res_cache_answer(Rec),
+ RRs = Rec#dns_rec.anlist,
+ ?dbg("res_hostent_by_domain: ~p - ~p~n", [Domain, RRs]),
+ res_hostent_by_domain(stripdot(Domain), [], Type, RRs).
+
+res_hostent_by_domain(Domain, Aliases, Type, RRs) ->
+ case res_lookup_type(Domain, Type, RRs) of
+ [] ->
+ case res_lookup_type(Domain, ?S_CNAME, RRs) of
+ [] ->
+ {error, nxdomain};
+ [CName | _] ->
+ case lists:member(CName, [Domain | Aliases]) of
+ true ->
+ {error, nxdomain};
+ false ->
+ res_hostent_by_domain(CName, [Domain | Aliases],
+ Type, RRs)
+ end
+ end;
+ Addrs ->
+ {ok, make_hostent(Domain, Addrs, Aliases, Type)}
+ end.
+
+%% newly resolved lookup address record
+res_lookup_type(Domain,Type,RRs) ->
+ [R#dns_rr.data || R <- RRs,
+ R#dns_rr.domain =:= Domain,
+ R#dns_rr.type =:= Type].
+
+%%
+%% gethostbyaddr (cache version)
+%% match data field directly
+%%
+gethostbyaddr(IP) ->
+ case dnip(IP) of
+ {ok, {IP1, HType, HLen, DnIP}} ->
+ RRs = match_rr(#dns_rr { domain = DnIP, class = in, type = ptr,
+ cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_', data = '_' }),
+ ent_gethostbyaddr(RRs, IP1, HType, HLen);
+ Error -> Error
+ end.
+
+%%
+%% res_gethostbyaddr (newly resolved version)
+%% match data field directly and cache RRs.
+%%
+res_gethostbyaddr(IP, Rec) ->
+ {ok, {IP1, HType, HLen}} = dnt(IP),
+ res_cache_answer(Rec),
+ ent_gethostbyaddr(Rec#dns_rec.anlist, IP1, HType, HLen).
+
+ent_gethostbyaddr(RRs, IP, AddrType, Length) ->
+ case RRs of
+ [] -> {error, nxdomain};
+ [RR|TR] ->
+ %% debug
+ if TR =/= [] ->
+ ?dbg("gethostbyaddr found extra=~p~n", [TR]);
+ true -> ok
+ end,
+ Domain = RR#dns_rr.data,
+ H = #hostent { h_name = Domain,
+ h_aliases = lookup_cname(Domain),
+ h_addr_list = [IP],
+ h_addrtype = AddrType,
+ h_length = Length },
+ {ok, H}
+ end.
+
+dnip(IP) ->
+ case dnt(IP) of
+ {ok,{IP1 = {A,B,C,D}, inet, HLen}} ->
+ {ok,{IP1, inet, HLen, dn_in_addr_arpa(A,B,C,D)}};
+ {ok,{IP1 = {A,B,C,D,E,F,G,H}, inet6, HLen}} ->
+ {ok,{IP1, inet6, HLen, dn_ip6_int(A,B,C,D,E,F,G,H)}};
+ _ ->
+ {error, formerr}
+ end.
+
+
+dnt(IP = {A,B,C,D}) when ?ip(A,B,C,D) ->
+ {ok, {IP, inet, 4}};
+dnt({0,0,0,0,0,16#ffff,G,H}) when is_integer(G+H) ->
+ A = G div 256, B = G rem 256, C = H div 256, D = H rem 256,
+ {ok, {{A,B,C,D}, inet, 4}};
+dnt(IP = {A,B,C,D,E,F,G,H}) when ?ip6(A,B,C,D,E,F,G,H) ->
+ {ok, {IP, inet6, 16}};
+dnt(_) ->
+ {error, formerr}.
+
+%%
+%% Register socket Modules
+%%
+register_socket(Socket, Module) when is_port(Socket), is_atom(Module) ->
+ try erlang:port_set_data(Socket, Module)
+ catch
+ error:badarg -> false
+ end.
+
+unregister_socket(Socket) when is_port(Socket) ->
+ ok. %% not needed any more
+
+lookup_socket(Socket) when is_port(Socket) ->
+ try erlang:port_get_data(Socket) of
+ Module when is_atom(Module) -> {ok,Module};
+ _ -> {error,closed}
+ catch
+ error:badarg -> {error,closed}
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+
+%% INET DB ENTRY TYPES:
+%%
+%% KEY VALUE - DESCRIPTION
+%%
+%% hostname String - SHORT host name
+%%
+%% Resolver options
+%% ----------------
+%% res_ns [Nameserver] - list of name servers
+%% res_alt_ns [AltNameServer] - list of alternate name servers (nxdomain)
+%% res_search [Domain] - list of domains for short names
+%% res_domain Domain - local domain for short names
+%% res_recurse Bool - recursive query
+%% res_usevc Bool - use tcp only
+%% res_id Integer - NS query identifier
+%% res_retry Integer - Retry count for UDP query
+%% res_timeout Integer - UDP query timeout before retry
+%% res_inet6 Bool - address family inet6 for gethostbyname/1
+%% res_usevc Bool - use Virtual Circuit (TCP)
+%% res_edns false|Integer - false or EDNS version
+%% res_udp_payload_size Integer - size for EDNS, both query and reply
+%% res_resolv_conf Filename - file to watch for resolver config i.e
+%% {res_ns, res_search}
+%% res_hosts_file Filename - file to watch for hosts config
+%%
+%% Socks5 options
+%% --------------
+%% socks5_server Server - IP address of the socks5 server
+%% socks5_port Port - TCP port of the socks5 server
+%% socks5_methods Ls - List of authentication methods
+%% socks5_noproxy IPs - List of {Net,Subnetmask}
+%%
+%% Generic tcp/udp options
+%% -----------------------
+%% tcp_module Module - The default gen_tcp module
+%% udp_module Module - The default gen_udp module
+%% sctp_module Module - The default gen_sctp module
+%%
+%% Distribution options
+%% --------------------
+%% {node_auth,N} Ls - List of authentication for node N
+%% {node_crypt,N} Ls - List of encryption methods for node N
+%% node_auth Ls - Default authenication
+%% node_crypt Ls - Default encryption
+%%
+init([]) ->
+ process_flag(trap_exit, true),
+ Db = ets:new(inet_db, [public, named_table]),
+ reset_db(Db),
+ Cache = ets:new(inet_cache, [public, bag, {keypos,2}, named_table]),
+ BynameOpts = [protected, bag, named_table, {keypos,1}],
+ ByaddrOpts = [protected, bag, named_table, {keypos,3}],
+ HostsByname = ets:new(inet_hosts_byname, BynameOpts),
+ HostsByaddr = ets:new(inet_hosts_byaddr, ByaddrOpts),
+ HostsFileByname = ets:new(inet_hosts_file_byname, BynameOpts),
+ HostsFileByaddr = ets:new(inet_hosts_file_byaddr, ByaddrOpts),
+ {ok, #state{db = Db,
+ cache = Cache,
+ hosts_byname = HostsByname,
+ hosts_byaddr = HostsByaddr,
+ hosts_file_byname = HostsFileByname,
+ hosts_file_byaddr = HostsFileByaddr,
+ cache_timer = init_timer() }}.
+
+reset_db(Db) ->
+ ets:insert(Db, {hostname, []}),
+ ets:insert(Db, {res_ns, []}),
+ ets:insert(Db, {res_alt_ns, []}),
+ ets:insert(Db, {res_search, []}),
+ ets:insert(Db, {res_domain, ""}),
+ ets:insert(Db, {res_lookup, []}),
+ ets:insert(Db, {res_recurse, true}),
+ ets:insert(Db, {res_usevc, false}),
+ ets:insert(Db, {res_id, 0}),
+ ets:insert(Db, {res_retry, ?RES_RETRY}),
+ ets:insert(Db, {res_timeout, ?RES_TIMEOUT}),
+ ets:insert(Db, {res_inet6, false}),
+ ets:insert(Db, {res_edns, false}),
+ ets:insert(Db, {res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE}),
+ ets:insert(Db, {cache_size, ?CACHE_LIMIT}),
+ ets:insert(Db, {cache_refresh_interval,?CACHE_REFRESH}),
+ ets:insert(Db, {socks5_server, ""}),
+ ets:insert(Db, {socks5_port, ?IPPORT_SOCKS}),
+ ets:insert(Db, {socks5_methods, [none]}),
+ ets:insert(Db, {socks5_noproxy, []}),
+ ets:insert(Db, {tcp_module, ?DEFAULT_TCP_MODULE}),
+ ets:insert(Db, {udp_module, ?DEFAULT_UDP_MODULE}),
+ ets:insert(Db, {sctp_module, ?DEFAULT_SCTP_MODULE}).
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, Reply, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_call(Request, From, #state{db=Db}=State) ->
+ case Request of
+ {load_hosts_file,IPNmAs} when is_list(IPNmAs) ->
+ NIPs = lists:flatten([ [{N,if tuple_size(IP) =:= 4 -> inet;
+ tuple_size(IP) =:= 8 -> inet6
+ end,IP} || N <- [Nm|As]]
+ || {IP,Nm,As} <- IPNmAs]),
+ Byname = State#state.hosts_file_byname,
+ Byaddr = State#state.hosts_file_byaddr,
+ ets:delete_all_objects(Byname),
+ ets:delete_all_objects(Byaddr),
+ ets:insert(Byname, NIPs),
+ ets:insert(Byaddr, NIPs),
+ {reply, ok, State};
+
+ {add_host,{A,B,C,D}=IP,[N|As]=Names}
+ when ?ip(A,B,C,D), is_list(N), is_list(As) ->
+ do_add_host(State#state.hosts_byname,
+ State#state.hosts_byaddr,
+ Names, inet, IP),
+ {reply, ok, State};
+ {add_host,{A,B,C,D,E,F,G,H}=IP,[N|As]=Names}
+ when ?ip6(A,B,C,D,E,F,G,H), is_list(N), is_list(As) ->
+ do_add_host(State#state.hosts_byname,
+ State#state.hosts_byaddr,
+ Names, inet6, IP),
+ {reply, ok, State};
+
+ {del_host,{A,B,C,D}=IP} when ?ip(A,B,C,D) ->
+ do_del_host(State#state.hosts_byname,
+ State#state.hosts_byaddr,
+ IP),
+ {reply, ok, State};
+ {del_host,{A,B,C,D,E,F,G,H}=IP} when ?ip6(A,B,C,D,E,F,G,H) ->
+ do_del_host(State#state.hosts_byname,
+ State#state.hosts_byaddr,
+ IP),
+ {reply, ok, State};
+
+ {add_rr, RR} when is_record(RR, dns_rr) ->
+ RR1 = lower_rr(RR),
+ ?dbg("add_rr: ~p~n", [RR1]),
+ do_add_rr(RR1, Db, State),
+ {reply, ok, State};
+
+ {del_rr, RR} when is_record(RR, dns_rr) ->
+ RR1 = lower_rr(RR),
+ %% note. del_rr will handle wildcards !!!
+ Cache = State#state.cache,
+ ets:match_delete(Cache, RR1),
+ {reply, ok, State};
+
+ {lookup_rr, Domain, Class, Type} ->
+ {reply, do_lookup_rr(Domain, Class, Type), State};
+
+ {listop, Opt, Op, E} ->
+ El = [E],
+ case res_check_option(Opt, El) of
+ true ->
+ Optname = res_optname(Opt),
+ [{_,Es}] = ets:lookup(Db, Optname),
+ NewEs = case Op of
+ ins -> [E | lists_delete(E, Es)];
+ add -> lists_delete(E, Es) ++ El;
+ del -> lists_delete(E, Es)
+ end,
+ ets:insert(Db, {Optname, NewEs}),
+ {reply,ok,State};
+ false ->
+ {reply,error,State}
+ end;
+
+ {listdel, Opt} ->
+ ets:insert(Db, {res_optname(Opt), []}),
+ {reply, ok, State};
+
+ {set_hostname, Name} ->
+ case inet_parse:visible_string(Name) of
+ true ->
+ ets:insert(Db, {hostname, Name}),
+ {reply, ok, State};
+ false ->
+ {reply, error, State}
+ end;
+
+ {res_set, hosts_file=Option, Fname} ->
+ handle_set_file(Option, Fname,
+ res_hosts_file_tm, res_hosts_file_info,
+ fun (Bin) ->
+ case inet_parse:hosts(Fname,
+ {chars,Bin}) of
+ {ok,Opts} ->
+ [{load_hosts_file,Opts}];
+ _ -> error
+ end
+ end,
+ From, State);
+ %%
+ {res_set, resolv_conf=Option, Fname} ->
+ handle_set_file(Option, Fname,
+ res_resolv_conf_tm, res_resolv_conf_info,
+ fun (Bin) ->
+ case inet_parse:resolv(Fname,
+ {chars,Bin}) of
+ {ok,Opts} ->
+ [del_ns,
+ clear_search,
+ clear_cache
+ |[Opt ||
+ {T,_}=Opt <- Opts,
+ (T =:= nameserver orelse
+ T =:= search)]];
+ _ -> error
+ end
+ end,
+ From, State);
+ %%
+ {res_set, Opt, Value} ->
+ case res_optname(Opt) of
+ undefined ->
+ {reply, error, State};
+ Optname ->
+ case res_check_option(Opt, Value) of
+ true ->
+ ets:insert(Db, {Optname, Value}),
+ {reply, ok, State};
+ false ->
+ {reply, error, State}
+ end
+ end;
+
+ {set_resolv_conf_tm, TM} ->
+ ets:insert(Db, {res_resolv_conf_tm, TM}),
+ {reply, ok, State};
+
+ {set_hosts_file_tm, TM} ->
+ ets:insert(Db, {res_hosts_file_tm, TM}),
+ {reply, ok, State};
+
+ {set_socks_server, {A,B,C,D}} when ?ip(A,B,C,D) ->
+ ets:insert(Db, {socks5_server, {A,B,C,D}}),
+ {reply, ok, State};
+
+ {set_socks_port, Port} when is_integer(Port) ->
+ ets:insert(Db, {socks5_port, Port}),
+ {reply, ok, State};
+
+ {add_socks_methods, Ls} ->
+ [{_,As}] = ets:lookup(Db, socks5_methods),
+ As1 = lists_subtract(As, Ls),
+ ets:insert(Db, {socks5_methods, As1 ++ Ls}),
+ {reply, ok, State};
+
+ {del_socks_methods, Ls} ->
+ [{_,As}] = ets:lookup(Db, socks5_methods),
+ As1 = lists_subtract(As, Ls),
+ case lists:member(none, As1) of
+ false -> ets:insert(Db, {socks5_methods, As1 ++ [none]});
+ true -> ets:insert(Db, {socks5_methods, As1})
+ end,
+ {reply, ok, State};
+
+ del_socks_methods ->
+ ets:insert(Db, {socks5_methods, [none]}),
+ {reply, ok, State};
+
+ {add_socks_noproxy, {{A,B,C,D},{MA,MB,MC,MD}}}
+ when ?ip(A,B,C,D), ?ip(MA,MB,MC,MD) ->
+ [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ ets:insert(Db, {socks5_noproxy, As++[{{A,B,C,D},{MA,MB,MC,MD}}]}),
+ {reply, ok, State};
+
+ {del_socks_noproxy, {A,B,C,D}=IP} when ?ip(A,B,C,D) ->
+ [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ ets:insert(Db, {socks5_noproxy, lists_keydelete(IP, 1, As)}),
+ {reply, ok, State};
+
+ {set_tcp_module, Mod} when is_atom(Mod) ->
+ ets:insert(Db, {tcp_module, Mod}), %% check/load module ?
+ {reply, ok, State};
+
+ {set_udp_module, Mod} when is_atom(Mod) ->
+ ets:insert(Db, {udp_module, Mod}), %% check/load module ?
+ {reply, ok, State};
+
+ {set_sctp_module, Fam} when is_atom(Fam) ->
+ ets:insert(Db, {sctp_module, Fam}), %% check/load module ?
+ {reply, ok, State};
+
+ {set_cache_size, Size} when is_integer(Size), Size >= 0 ->
+ ets:insert(Db, {cache_size, Size}),
+ {reply, ok, State};
+
+ {set_cache_refresh, Time} when is_integer(Time), Time > 0 ->
+ Time1 = ((Time+999) div 1000)*1000, %% round up
+ ets:insert(Db, {cache_refresh_interval, Time1}),
+ stop_timer(State#state.cache_timer),
+ {reply, ok, State#state{cache_timer = init_timer()}};
+
+ clear_hosts ->
+ ets:delete_all_objects(State#state.hosts_byname),
+ ets:delete_all_objects(State#state.hosts_byaddr),
+ {reply, ok, State};
+
+ clear_cache ->
+ ets:match_delete(State#state.cache, '_'),
+ {reply, ok, State};
+
+ reset ->
+ reset_db(Db),
+ stop_timer(State#state.cache_timer),
+ {reply, ok, State#state{cache_timer = init_timer()}};
+
+ {add_rc_list, List} ->
+ handle_rc_list(List, From, State);
+
+ stop ->
+ {stop, normal, ok, State};
+
+ _ ->
+ {reply, error, State}
+ end.
+
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_info(refresh_timeout, State) ->
+ do_refresh_cache(State#state.cache),
+ {noreply, State#state{cache_timer = init_timer()}};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+terminate(_Reason, State) ->
+ stop_timer(State#state.cache_timer),
+ ok.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From,
+ #state{db=Db}=State) ->
+ case res_check_option(Option, Fname) of
+ true when Fname =:= "" ->
+ ets:insert(Db, {res_optname(Option), Fname}),
+ ets:delete(Db, TagInfo),
+ ets:delete(Db, TagTm),
+ handle_set_file(ParseFun, <<>>, From, State);
+ true ->
+ File = filename:flatten(Fname),
+ ets:insert(Db, {res_optname(Option), File}),
+ Bin =
+ case erl_prim_loader:read_file_info(File) of
+ {ok, Finfo0} ->
+ Finfo = Finfo0#file_info{access = undefined,
+ atime = undefined},
+ ets:insert(Db, {TagInfo, Finfo}),
+ ets:insert(Db, {TagTm, times()}),
+ case erl_prim_loader:get_file(File) of
+ {ok, B, _} -> B;
+ _ -> <<>>
+ end;
+ _ -> <<>>
+ end,
+ handle_set_file(ParseFun, Bin, From, State);
+ false -> {reply,error,State}
+ end.
+
+handle_set_file(ParseFun, Bin, From, State) ->
+ case ParseFun(Bin) of
+ error -> {reply,error,State};
+ Opts ->
+ handle_rc_list(Opts, From, State)
+ end.
+
+do_add_host(Byname, Byaddr, Names, Type, IP) ->
+ do_del_host(Byname, Byaddr, IP),
+ NIPs = [{tolower(N),Type,IP} || N <- Names],
+ ets:insert(Byname, NIPs),
+ ets:insert(Byaddr, NIPs),
+ ok.
+
+do_del_host(Byname, Byaddr, IP) ->
+ [ets:delete_object(Byname, NIP) || NIP <- ets:lookup(Byaddr, IP)],
+ ets:delete(Byaddr, IP),
+ ok.
+
+%% Loop over .inetrc option list and call handle_call/3 for each
+%%
+handle_rc_list([], _From, State) ->
+ {reply, ok, State};
+handle_rc_list([Opt|Opts], From, State) ->
+ case rc_opt_req(Opt) of
+ undefined ->
+ {reply, {error,{badopt,Opt}}, State};
+ Req ->
+ case handle_calls(Req, From, State) of
+ {reply, ok, NewState} ->
+ handle_rc_list(Opts, From, NewState);
+ Result -> Result
+ end
+ end;
+handle_rc_list(_, _From, State) ->
+ {reply, error, State}.
+
+handle_calls([], _From, State) ->
+ {reply, ok, State};
+handle_calls([Req|Reqs], From, State) ->
+ case handle_call(Req, From, State) of
+ {reply, ok, NewState} ->
+ handle_calls(Reqs, From, NewState);
+ {reply, _, NewState} ->
+ {reply, error, NewState}
+ %% {noreply,_} is currently not returned by handle_call/3
+ end;
+handle_calls(Req, From, State) ->
+ handle_call(Req, From, State).
+
+%% Translate .inetrc option into gen_server request
+%%
+rc_opt_req({nameserver, Ns}) ->
+ {listop,nameservers,add,{Ns,?NAMESERVER_PORT}};
+rc_opt_req({nameserver, Ns, Port}) ->
+ {listop,nameservers,add,{Ns,Port}};
+rc_opt_req({alt_nameserver, Ns}) ->
+ {listop,alt_nameservers,add,{Ns,?NAMESERVER_PORT}};
+rc_opt_req({alt_nameserver, Ns, Port}) ->
+ {listop,alt_nameservers,add,{Ns,Port}};
+rc_opt_req({socks5_noproxy, IP, Mask}) ->
+ {add_socks_noproxy, {IP, Mask}};
+rc_opt_req({search, Ds}) when is_list(Ds) ->
+ try [{listop,search,add,D} || D <- Ds]
+ catch error:_ -> undefined
+ end;
+rc_opt_req({host, IP, Aliases}) -> {add_host, IP, Aliases};
+rc_opt_req({load_hosts_file, _}=Req) -> Req;
+rc_opt_req({lookup, Ls}) ->
+ try {res_set, lookup, translate_lookup(Ls)}
+ catch error:_ -> undefined
+ end;
+rc_opt_req({Name,Arg}) ->
+ case rc_reqname(Name) of
+ undefined ->
+ case is_res_set(Name) of
+ true -> {res_set,Name,Arg};
+ false -> undefined
+ end;
+ Req -> {Req, Arg}
+ end;
+rc_opt_req(del_ns) ->
+ {listdel,nameservers};
+rc_opt_req(del_alt_ns) ->
+ {listdel,alt_nameservers};
+rc_opt_req(clear_ns) ->
+ [{listdel,nameservers},{listdel,alt_nameservers}];
+rc_opt_req(clear_search) ->
+ {listdel,search};
+rc_opt_req(Opt) when is_atom(Opt) ->
+ case is_reqname(Opt) of
+ true -> Opt;
+ false -> undefined
+ end;
+rc_opt_req(_) -> undefined.
+%%
+rc_reqname(socks5_server) -> set_socks_server;
+rc_reqname(socks5_port) -> set_socks_port;
+rc_reqname(socks5_methods) -> set_socks_methods;
+rc_reqname(cache_refresh) -> set_cache_refresh;
+rc_reqname(cache_size) -> set_cache_size;
+rc_reqname(udp) -> set_udp_module;
+rc_reqname(sctp) -> set_sctp_module;
+rc_reqname(tcp) -> set_tcp_module;
+rc_reqname(_) -> undefined.
+%%
+is_res_set(domain) -> true;
+is_res_set(lookup) -> true;
+is_res_set(timeout) -> true;
+is_res_set(retry) -> true;
+is_res_set(inet6) -> true;
+is_res_set(usevc) -> true;
+is_res_set(edns) -> true;
+is_res_set(udp_payload_size) -> true;
+is_res_set(resolv_conf) -> true;
+is_res_set(hosts_file) -> true;
+is_res_set(_) -> false.
+%%
+is_reqname(reset) -> true;
+is_reqname(clear_cache) -> true;
+is_reqname(clear_hosts) -> true;
+is_reqname(_) -> false.
+
+%% Add a resource record to the cache if there are space left.
+%% If the cache is full this function first deletes old entries,
+%% i.e. entries with oldest latest access time.
+%% #dns_rr.cnt is used to store the access time instead of number of
+%% accesses.
+do_add_rr(RR, Db, State) ->
+ CacheDb = State#state.cache,
+ TM = times(),
+ case alloc_entry(Db, CacheDb, TM) of
+ true ->
+ cache_rr(Db, CacheDb, RR#dns_rr { tm = TM,
+ cnt = TM });
+ _ ->
+ false
+ end.
+
+cache_rr(_Db, Cache, RR) ->
+ %% delete possible old entry
+ ets:match_delete(Cache, RR#dns_rr { cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_'}),
+ ets:insert(Cache, RR).
+
+times() ->
+ {Mega,Secs,_} = erlang:now(),
+ Mega*1000000 + Secs.
+
+%% lookup and remove old entries
+
+do_lookup_rr(Domain, Class, Type) ->
+ match_rr(#dns_rr { domain = tolower(Domain), class = Class,type = Type,
+ cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_', data = '_'}).
+
+match_rr(RR) ->
+ filter_rr(ets:match_object(inet_cache, RR), times()).
+
+
+%% filter old resource records and update access count
+
+filter_rr([RR | RRs], Time) when RR#dns_rr.ttl =:= 0 -> %% at least once
+ ets:match_delete(inet_cache, RR),
+ [RR | filter_rr(RRs, Time)];
+filter_rr([RR | RRs], Time) when RR#dns_rr.tm + RR#dns_rr.ttl < Time ->
+ ets:match_delete(inet_cache, RR),
+ filter_rr(RRs, Time);
+filter_rr([RR | RRs], Time) ->
+ ets:match_delete(inet_cache, RR),
+ ets:insert(inet_cache, RR#dns_rr { cnt = Time }),
+ [RR | filter_rr(RRs, Time)];
+filter_rr([], _Time) -> [].
+
+
+%%
+%% Lower case the domain name before storage
+%%
+lower_rr(RR) ->
+ Dn = RR#dns_rr.domain,
+ if is_list(Dn) ->
+ RR#dns_rr { domain = tolower(Dn) };
+ true -> RR
+ end.
+
+%%
+%% Map upper-case to lower-case
+%% NOTE: this code is in kernel and we don't want to relay
+%% to much on stdlib
+%%
+tolower([]) -> [];
+tolower([C|Cs]) when C >= $A, C =< $Z -> [(C-$A)+$a|tolower(Cs)];
+tolower([C|Cs]) -> [C|tolower(Cs)].
+
+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.int".
+
+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".
+
+dnib(X) ->
+ [ hex(X), $., hex(X bsr 4), $., hex(X bsr 8), $., hex(X bsr 12), $.].
+
+hex(X) ->
+ X4 = (X band 16#f),
+ if X4 < 10 -> X4 + $0;
+ true -> (X4-10) + $a
+ end.
+
+%% Strip trailing dot, do not produce garbage unless necessary.
+%%
+stripdot(Name) ->
+ case stripdot_1(Name) of
+ false -> Name;
+ N -> N
+ end.
+%%
+stripdot_1([$.]) -> [];
+stripdot_1([]) -> false;
+stripdot_1([H|T]) ->
+ case stripdot_1(T) of
+ false -> false;
+ N -> [H|N]
+ end.
+
+%% -------------------------------------------------------------------
+%% Refresh cache at regular intervals, i.e. delete expired #dns_rr's.
+%% -------------------------------------------------------------------
+init_timer() ->
+ erlang:send_after(cache_refresh(), self(), refresh_timeout).
+
+stop_timer(undefined) ->
+ undefined;
+stop_timer(Timer) ->
+ erlang:cancel_timer(Timer).
+
+cache_refresh() ->
+ case db_get(cache_refresh_interval) of
+ undefined -> ?CACHE_REFRESH;
+ Val -> Val
+ end.
+
+%% Delete all entries with expired TTL.
+%% Returns the access time of the entry with the oldest access time
+%% in the cache.
+do_refresh_cache(CacheDb) ->
+ Now = times(),
+ do_refresh_cache(ets:first(CacheDb), CacheDb, Now, Now).
+
+do_refresh_cache('$end_of_table', _, _, OldestT) ->
+ OldestT;
+do_refresh_cache(Key, CacheDb, Now, OldestT) ->
+ Fun = fun(RR, T) when RR#dns_rr.tm + RR#dns_rr.ttl < Now ->
+ ets:match_delete(CacheDb, RR),
+ T;
+ (#dns_rr{cnt = C}, T) when C < T ->
+ C;
+ (_, T) ->
+ T
+ end,
+ Next = ets:next(CacheDb, Key),
+ OldT = lists:foldl(Fun, OldestT, ets:lookup(CacheDb, Key)),
+ do_refresh_cache(Next, CacheDb, Now, OldT).
+
+%% -------------------------------------------------------------------
+%% Allocate room for a new entry in the cache.
+%% Deletes entries with expired TTL and all entries with latest
+%% access time older than
+%% trunc((TM - OldestTM) * 0.3) + OldestTM from the cache if it
+%% is full. Does not delete more than 10% of the entries in the cache
+%% though, unless they there deleted due to expired TTL.
+%% Returns: true if space for a new entry otherwise false.
+%% -------------------------------------------------------------------
+alloc_entry(Db, CacheDb, TM) ->
+ CurSize = ets:info(CacheDb, size),
+ case ets:lookup(Db, cache_size) of
+ [{cache_size, Size}] when Size =< CurSize, Size > 0 ->
+ alloc_entry(CacheDb, CurSize, TM, trunc(Size * 0.1) + 1);
+ [{cache_size, Size}] when Size =< 0 ->
+ false;
+ _ ->
+ true
+ end.
+
+alloc_entry(CacheDb, OldSize, TM, N) ->
+ OldestTM = do_refresh_cache(CacheDb), % Delete timedout entries
+ case ets:info(CacheDb, size) of
+ OldSize ->
+ %% No entrys timedout
+ delete_n_oldest(CacheDb, TM, OldestTM, N);
+ _ ->
+ true
+ end.
+
+delete_n_oldest(CacheDb, TM, OldestTM, N) ->
+ DelTM = trunc((TM - OldestTM) * 0.3) + OldestTM,
+ case delete_older(CacheDb, DelTM, N) of
+ 0 ->
+ false;
+ _ ->
+ true
+ end.
+
+%% Delete entries with latest access time older than TM.
+%% Delete max N number of entries.
+%% Returns the number of deleted entries.
+delete_older(CacheDb, TM, N) ->
+ delete_older(ets:first(CacheDb), CacheDb, TM, N, 0).
+
+delete_older('$end_of_table', _, _, _, M) ->
+ M;
+delete_older(_, _, _, N, M) when N =< M ->
+ M;
+delete_older(Domain, CacheDb, TM, N, M) ->
+ Next = ets:next(CacheDb, Domain),
+ Fun = fun(RR, MM) when RR#dns_rr.cnt =< TM ->
+ ets:match_delete(CacheDb, RR),
+ MM + 1;
+ (_, MM) ->
+ MM
+ end,
+ M1 = lists:foldl(Fun, M, ets:lookup(CacheDb, Domain)),
+ delete_older(Next, CacheDb, TM, N, M1).
+
+
+%% as lists:delete/2, but delete all exact matches
+%%
+lists_delete(_, []) -> [];
+lists_delete(E, [E|Es]) ->
+ lists_delete(E, Es);
+lists_delete(E, [X|Es]) ->
+ [X|lists_delete(E, Es)].
+
+%% as '--'/2 aka lists:subtract/2 but delete all exact matches
+lists_subtract(As0, Bs) ->
+ lists:foldl(fun (E, As) -> lists_delete(E, As) end, As0, Bs).
+
+%% as lists:keydelete/3, but delete all _exact_ key matches
+lists_keydelete(_, _, []) -> [];
+lists_keydelete(K, N, [T|Ts]) when element(N, T) =:= K ->
+ lists_keydelete(K, N, Ts);
+lists_keydelete(K, N, [X|Ts]) ->
+ [X|lists_keydelete(K, N, Ts)].