Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2021. 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_db).
%% Store info about ip addresses, names, aliases host files resolver
%% options.
%% Also miscellaneous "stuff" related to sockets.
%% 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]).
-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]).
-export([add_search/1,ins_search/1,del_search/1]).
-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_servfail_retry_timeout/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,
put_socket_type/2, take_socket_type/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
sockets, %% hosts table from system file
cache_timer %% timer reference for refresh
}).
-type state() :: #state{}.
-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}=Ok -> inet_config:init(), Ok;
Error -> Error
end.
start_link() ->
case gen_server:start_link({local, inet_db}, inet_db, [], []) of
{ok, _Pid}=Ok -> inet_config:init(), Ok;
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}}).
%% 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}}).
%% 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}).
%% 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_servfail_retry_timeout(Time) when is_integer(Time) andalso (Time >= 0) ->
res_option(servfail_retry_timeout, Time).
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) when is_list(Fname) ->
res_option(resolv_conf, Fname).
set_hosts_file(Fname) when is_list(Fname) ->
res_option(hosts_file, Fname).
get_hosts_file() ->
get_rc_hosts([], [], inet_hosts_file_byaddr).
%% 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, servfail_retry_timeout, 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_byaddr);
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);
servfail_retry_timeout -> get_rc(servfail_retry_timeout,
res_servfail_retry_timeout,
?RES_SERVFAIL_RETRY_TO,
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) ->
get_rc(Ks, get_rc_hosts(ets:tab2list(Tab), Ls)).
get_rc_hosts([], Ls) ->
Ls;
get_rc_hosts([{{_Fam, IP}, Names} | Hosts], Ls) ->
get_rc_hosts(Hosts, [{host, IP, Names} | Ls]).
%%
%% 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(servfail_retry_timeout) -> res_servfail_retry_timeout;
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(resolv_conf_name) -> res_resolv_conf;
res_optname(hosts_file) -> res_hosts_file;
res_optname(hosts_file_name) -> 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(servfail_retry_timeout, T) when is_integer(T), T >= 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(resolv_conf_name, "") -> true;
res_check_option(resolv_conf_name, 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(hosts_file_name, "") -> true;
res_check_option(hosts_file_name, 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) ->
Fun(H) andalso res_check_list(T, Fun);
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(resolv_conf, res_resolv_conf_tm).
res_update_hosts() ->
res_update(hosts_file, res_hosts_file_tm).
res_update(Option, TagTm) ->
case db_get(TagTm) of
undefined -> ok;
Tm ->
case times() of
Now when Now >= Tm + ?RES_FILE_UPDATE_TM ->
%% Enough time has passed - request server to update
res_option(Option, Tm);
_ -> ok
end
end.
db_get(Name) ->
try ets:lookup_element(inet_db, Name, 2)
catch error:badarg -> undefined
end.
add_rr(RR) ->
call({add_rr, RR}).
add_rr(Domain, Class, Type, TTL, Data) ->
call({add_rr, dns_rr_add(Domain, Class, Type, TTL, Data)}).
del_rr(Domain, Class, Type, Data) ->
call({del_rr, dns_rr_match(Domain, Class, Type, 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 -> Ok;
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, LAliases, Type) ->
case lookup_type(Domain, Type) of
[] ->
case lookup_cname(Domain) of
[] ->
{error, nxdomain};
[CName | _] ->
LDomain = tolower(Domain),
case lists:member(CName, [LDomain | LAliases]) of
true ->
{error, nxdomain};
false ->
hostent_by_domain(CName, [Domain | Aliases],
[LDomain | LAliases], 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) ].
lookup_cname(Domain, Type) ->
case Type of
a -> [];
aaaa -> [];
cname -> lookup_cname(Domain);
_ -> []
end.
%% lookup resource record
lookup_rr(Domain, Class, Type) ->
match_rr(dns_rr_match(tolower(Domain), Class, Type)).
%%
%% hostent_by_domain (newly resolved version)
%% match data field directly and cache RRs.
%%
res_hostent_by_domain(Domain, Type, Rec) ->
RRs = lists:map(fun lower_rr/1, Rec#dns_rec.anlist),
res_cache_answer(Rec#dns_rec{anlist = RRs}),
?dbg("res_hostent_by_domain: ~p - ~p~n", [Domain, RRs]),
res_hostent_by_domain(stripdot(Domain), [], [], Type, RRs).
res_hostent_by_domain(Domain, Aliases, LAliases, Type, RRs) ->
LDomain = tolower(Domain),
case res_lookup_type(LDomain, Type, RRs) of
[] ->
case res_lookup_type(LDomain, ?S_CNAME, RRs) of
[] ->
{error, nxdomain};
[CName | _] ->
case lists:member(tolower(CName), [LDomain | LAliases]) of
true ->
{error, nxdomain};
false ->
res_hostent_by_domain(CName, [Domain | Aliases],
[LDomain | LAliases], 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_match(DnIP, in, ptr)),
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),
RRs = lists:map(fun lower_rr/1, Rec#dns_rec.anlist),
res_cache_answer(Rec#dns_rec{anlist = RRs}),
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,
Type = RR#dns_rr.type,
Domain = RR#dns_rr.data,
H = #hostent { h_name = Domain,
h_aliases = lookup_cname(Domain, Type),
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.
put_socket_type(MRef, Type) ->
call({put_socket_type, MRef, Type}).
take_socket_type(MRef) ->
call({take_socket_type, MRef}).
%%%----------------------------------------------------------------------
%%% 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_servfail_retry_timeout Integer - Timeout to next query after a failure
%% 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
%%
%% Socket type (used for socket monitors)
%% --------------------------------------
%% reference() inet | {socket, Module} - Type of socket being monitored
%%
-spec init([]) -> {'ok', state()}.
init([]) ->
process_flag(trap_exit, true),
case application:get_env(kernel, inet_backend) of
{ok, Flag}
when Flag =:= inet;
Flag =:= socket ->
persistent_term:put({kernel, inet_backend}, Flag);
_ -> ok
end,
Db = ets:new(inet_db, [public, named_table]),
reset_db(Db),
CacheOpts = [public, bag, {keypos,#dns_rr.domain}, named_table],
Cache = ets:new(inet_cache, CacheOpts),
HostsByname = ets:new(inet_hosts_byname, [named_table]),
HostsByaddr = ets:new(inet_hosts_byaddr, [named_table]),
HostsFileByname = ets:new(inet_hosts_file_byname, [named_table]),
HostsFileByaddr = ets:new(inet_hosts_file_byaddr, [named_table]),
%% Miscellaneous stuff related to sockets (monitoring, ...)
Sockets = ets:new(inet_sockets, [protected, set, named_table]),
{ok, #state{db = Db,
cache = Cache,
hosts_byname = HostsByname,
hosts_byaddr = HostsByaddr,
hosts_file_byname = HostsFileByname,
hosts_file_byaddr = HostsFileByaddr,
sockets = Sockets,
cache_timer = init_timer() }}.
reset_db(Db) ->
ets:insert(
Db,
[{hostname, []},
{res_ns, []},
{res_alt_ns, []},
{res_search, []},
{res_domain, ""},
{res_lookup, []},
{res_recurse, true},
{res_usevc, false},
{res_id, 0},
{res_retry, ?RES_RETRY},
{res_servfail_retry_timeout, ?RES_SERVFAIL_RETRY_TO},
{res_timeout, ?RES_TIMEOUT},
{res_inet6, false},
{res_edns, false},
{res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE},
{cache_size, ?CACHE_LIMIT},
{cache_refresh_interval,?CACHE_REFRESH},
{socks5_server, ""},
{socks5_port, ?IPPORT_SOCKS},
{socks5_methods, [none]},
{socks5_noproxy, []},
{tcp_module, ?DEFAULT_TCP_MODULE},
{udp_module, ?DEFAULT_UDP_MODULE},
{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)
%%----------------------------------------------------------------------
-spec handle_call(term(), {pid(), term()}, state()) ->
{'reply', term(), state()} | {'stop', 'normal', 'ok', state()}.
handle_call(Request, From, #state{db=Db}=State) ->
case Request of
{load_hosts_file,IPNmAs} when is_list(IPNmAs) ->
load_hosts_list(IPNmAs, State#state.hosts_file_byname, State#state.hosts_file_byaddr),
{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) ->
?dbg("add_rr: ~p~n", [RR]),
do_add_rr(RR, Db, State),
{reply, ok, State};
{del_rr, RR} when is_record(RR, dns_rr) ->
Cache = State#state.cache,
ets:match_delete(Cache, RR),
{reply, ok, State};
{listop, Opt, Op, E} ->
El = [E],
case res_check_option(Opt, El) of
true ->
Optname = res_optname(Opt),
Es = ets:lookup_element(Db, Optname, 2),
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;
{listreplace, Opt, Els} ->
case res_check_option(Opt, Els) of
true ->
ets:insert(Db, {res_optname(Opt), Els}),
{reply,ok,State};
false ->
{reply,error,State}
end;
{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_name=Option, Fname} ->
handle_set_file(
Option, Fname, res_hosts_file_tm, res_hosts_file_info,
undefined, From, State);
{res_set, resolv_conf_name=Option, Fname} ->
handle_set_file(
Option, Fname, res_resolv_conf_tm, res_resolv_conf_info,
undefined, From, State);
{res_set, hosts_file=Option, Fname_or_Tm} ->
handle_set_file(
Option, Fname_or_Tm, res_hosts_file_tm, res_hosts_file_info,
fun (File, Bin) ->
case inet_parse:hosts(
File, {chars,Bin}) of
{ok,Opts} ->
[{load_hosts_file,Opts}];
_ -> error
end
end,
From, State);
%%
{res_set, resolv_conf=Option, Fname_or_Tm} ->
handle_set_file(
Option, Fname_or_Tm, res_resolv_conf_tm, res_resolv_conf_info,
fun (File, Bin) ->
case inet_parse:resolv(
File, {chars,Bin}) of
{ok,Opts} ->
Search =
lists:foldl(
fun ({search,L}, _) ->
L;
({domain,""}, S) ->
S;
({domain,D}, _) ->
[D];
(_, S) ->
S
end, [], Opts),
NSs = [{NS,?NAMESERVER_PORT} || {nameserver,NS} <- Opts],
[{replace_search,Search},
{replace_ns,NSs},
clear_cache];
_ -> 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_element(Db, socks5_methods, 2),
As1 = lists_subtract(As, Ls),
ets:insert(Db, {socks5_methods, As1 ++ Ls}),
{reply, ok, State};
{del_socks_methods, Ls} ->
As = ets:lookup_element(Db, socks5_methods, 2),
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_element(Db, socks5_noproxy, 2),
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_element(Db, socks5_noproxy, 2),
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:delete_all_objects(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);
%% Store the type of socket this monitor (reference) refers to
{put_socket_type, MRef, Type} ->
Reply = handle_put_socket_type(State#state.sockets, MRef, Type),
{reply, Reply, State};
%% Take (in the 'maps' sence of the word) the socket type of
%% this socket monitor (reference).
{take_socket_type, MRef} ->
Reply = handle_take_socket_type(State#state.sockets, MRef),
{reply, Reply, 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)
%%----------------------------------------------------------------------
-spec handle_cast(term(), state()) -> {'noreply', state()}.
handle_cast(_Msg, State) ->
{noreply, State}.
%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
-spec handle_info(term(), state()) -> {'noreply', state()}.
handle_info(refresh_timeout, State) ->
_ = delete_expired(State#state.cache, times()),
{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)
%%----------------------------------------------------------------------
-spec terminate(term(), state()) -> 'ok'.
terminate(_Reason, State) ->
_ = stop_timer(State#state.cache_timer),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
handle_set_file(
Option, Tm, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State)
when is_integer(Tm) ->
%%
%% Maybe update file content
%%
try ets:lookup_element(Db, TagTm, 2) of
Tm ->
%% Current update request
File = ets:lookup_element(Db, res_optname(Option), 2),
Finfo = ets:lookup_element(Db, TagInfo, 2),
handle_update_file(
Finfo, File, TagTm, TagInfo, ParseFun, From, State);
_ ->
%% Late request - ignore update
{reply, ok, State}
catch error:badarg ->
%% Option no longer set - ignore update
{reply, ok, State}
end;
handle_set_file(
Option, Fname, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State) ->
case res_check_option(Option, Fname) of
true when Fname =:= "" ->
%% Delete file content and monitor
ets:insert(Db, {res_optname(Option), Fname}),
ets:delete(Db, TagInfo),
ets:delete(Db, TagTm),
handle_set_file(ParseFun, Fname, <<>>, From, State);
true when ParseFun =:= undefined ->
%% Set file name and monitor
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
ets:insert(Db, {TagInfo, undefined}),
TimeZero = times() - (?RES_FILE_UPDATE_TM + 1), % Early enough
ets:insert(Db, {TagTm, TimeZero}),
{reply,ok,State};
true ->
%% Set file name and monitor, read content
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
handle_update_file(
undefined, File, TagTm, TagInfo, ParseFun, From, State);
false -> {reply,error,State}
end.
handle_set_file(ParseFun, File, Bin, From, State) ->
case ParseFun(File, Bin) of
error ->
{reply,error,State};
Opts ->
handle_rc_list(Opts, From, State)
end.
handle_update_file(
Finfo, File, TagTm, TagInfo, ParseFun, From, #state{db = Db} = State) ->
ets:insert(Db, {TagTm, times()}),
%%
%% Update file content if file has been updated
%%
case erl_prim_loader:read_file_info(File) of
{ok, Finfo} ->
%% No file update - we are done
{reply, ok, State};
{ok, Finfo_1} ->
%% File updated - read content
ets:insert(Db, {TagInfo, Finfo_1}),
Bin =
case erl_prim_loader:get_file(File) of
{ok, B, _} -> B;
_ -> <<>>
end,
handle_set_file(ParseFun, File, Bin, From, State);
_ ->
%% No file - clear content and reset monitor
ets:insert(Db, {TagInfo, undefined}),
handle_set_file(ParseFun, File, <<>>, From, State)
end.
%% Byname has lowercased names while Byaddr keep the name casing.
%% This is to be able to reconstruct the original /etc/hosts entry.
do_add_host(Byname, Byaddr, Names, Type, IP) ->
Nms = [tolower(Nm) || Nm <- Names],
add_ip_bynms(Byname, Type, IP, Nms, Names),
Key = {Type, IP},
try ets:lookup_element(Byaddr, Key, 2) of
Names_0 ->
%% Delete IP address from byname entries
NmsSet = % Set of new tolower(Name)s
lists:foldl(
fun (Nm, Set) ->
maps:put(Nm, [], Set)
end, #{}, Nms),
del_ip_bynms(
Byname, Type, IP,
[Nm || Nm <- [tolower(Name) || Name <- Names_0],
not maps:is_key(Nm, NmsSet)])
catch error:badarg ->
ok
end,
%% Replace the entry in the byaddr table
ets:insert(Byaddr, {Key, Names}),
ok.
do_del_host(Byname, Byaddr, IP) ->
Fam = inet_family(IP),
Key = {Fam, IP},
try ets:lookup_element(Byaddr, Key, 2) of
Names ->
%% Delete IP address from byname entries
del_ip_bynms(
Byname, Fam, IP,
[tolower(Name) || Name <- Names]),
%% Delete from byaddr table
true = ets:delete(Byaddr, Key),
ok
catch error:badarg ->
ok
end.
add_ip_bynms(Byname, Fam, IP, Nms, Names) ->
lists:foreach(
fun (Nm) ->
Key = {Fam, Nm},
case ets:lookup(Byname, Key) of
[{_Key, [IP | _] = IPs, _Names_1}] ->
%% Replace names in the byname entry
true =
ets:insert(
Byname,
{Key, IPs, Names});
[{_Key, IPs, Names_0}] ->
case lists:member(IP, IPs) of
true ->
ok;
false ->
%% Add the IP address
true =
ets:insert(
Byname,
{Key, IPs ++ [IP], Names_0})
end;
[] ->
%% Create a new byname entry
true =
ets:insert(Byname, {Key, [IP], Names})
end
end, Nms).
del_ip_bynms(Byname, Fam, IP, Nms) ->
lists:foreach(
fun (Nm) ->
Key = {Fam, Nm},
case ets:lookup(Byname, Key) of
[{_Key, [IP], _Names}] ->
%% Delete whole entry
true = ets:delete(Byname, Key);
[{_Key, IPs_0, Names_0}] ->
case lists:member(IP, IPs_0) of
true ->
%% Delete the IP address from list
IPs = lists:delete(IP, IPs_0),
true =
ets:insert(
Byname, {Key, IPs, Names_0});
false ->
ok
end;
[] ->
ok
end
end, Nms).
inet_family(T) when tuple_size(T) =:= 4 -> inet;
inet_family(T) when tuple_size(T) =:= 8 -> inet6.
%% Hosts = [ {IP, Name, Aliases}, ... ]
%% ByaddrMap = #{ {Fam, IP} := rev(Names) }
%% BynameMap = #{ {Fam, tolower(Name)} := {rev([IP, ...]), Names}}
%% Synchronises internal tables with .hosts/aliases file
load_hosts_list(Hosts, Byname, Byaddr) ->
%% Create byaddr and byname maps
{ByaddrMap, BynameMap} = load_hosts_list(Hosts),
%% Insert or overwrite existing keys
ets:insert(
Byaddr,
[{Addr, lists:reverse(NamesR)}
|| {Addr, NamesR} <- maps:to_list(ByaddrMap)]),
ets:insert(
Byname,
[{Fam_Nm, lists:reverse(IPsR), Names}
|| {Fam_Nm, {IPsR, Names}} <- maps:to_list(BynameMap)]),
%% Delete no longer existing keys
ets_clean_map_keys(Byaddr, ByaddrMap),
ets_clean_map_keys(Byname, BynameMap).
load_hosts_list(Hosts) ->
load_hosts_list_byaddr(Hosts, #{}, []).
load_hosts_list_byaddr(
[], ByaddrMap, Addrs) ->
%% Now for the byname table...
load_hosts_list_byname(lists:reverse(Addrs), ByaddrMap, #{});
%% Traverse hosts list, create byaddr map and insertion order list
load_hosts_list_byaddr(
[{IP, Name, Aliases} | Hosts], ByaddrMap, Addrs) ->
Addr = {inet_family(IP), IP},
case ByaddrMap of
#{Addr := NamesR} ->
%% Concatenate names to existing IP address entry
load_hosts_list_byaddr(
Hosts,
ByaddrMap#{Addr := lists:reverse(Aliases, [Name | NamesR])},
Addrs);
#{} ->
%% First entry for an IP address
load_hosts_list_byaddr(
Hosts,
ByaddrMap#{Addr => lists:reverse(Aliases, [Name])},
[Addr | Addrs])
end.
%% Traverse in insertion order from byaddr pass
load_hosts_list_byname(
[], ByaddrMap, BynameMap) ->
{ByaddrMap, BynameMap};
load_hosts_list_byname(
[{Fam, IP} = Addr | Addrs], ByaddrMap, BynameMap) ->
Names = lists:reverse(maps:get(Addr, ByaddrMap)),
%% Traverse all names for this IP address
load_hosts_list_byname(
Addrs, ByaddrMap,
load_hosts_list_byname(Fam, IP, BynameMap, Names, Names)).
load_hosts_list_byname(_Fam, _IP, BynameMap, _Names_0, []) ->
BynameMap;
load_hosts_list_byname(
Fam, IP, BynameMap, Names_0, [Name | Names]) ->
Key = {Fam, tolower(Name)},
case BynameMap of
#{Key := {IPsR, Names_1}} ->
%% Add IP address to existing name entry
load_hosts_list_byname(
Fam, IP,
BynameMap#{Key := {[IP | IPsR], Names_1}},
Names_0, Names);
#{} ->
%% First entry for a name
load_hosts_list_byname(
Fam, IP,
BynameMap#{Key => {[IP], Names_0}},
Names_0, Names)
end.
ets_clean_map_keys(Tab, Map) ->
true = ets:safe_fixtable(Tab, true),
ets_clean_map_keys(Tab, Map, ets:first(Tab)),
true = ets:safe_fixtable(Tab, false),
ok.
%%
ets_clean_map_keys(_Tab, _Map, '$end_of_table') ->
ok;
ets_clean_map_keys(Tab, Map, Key) ->
case maps:is_key(Key, Map) of
true ->
ets_clean_map_keys(Tab, Map, ets:next(Tab, Key));
false ->
true = ets:delete(Tab, Key),
ets_clean_map_keys(Tab, Map, ets:next(Tab, Key))
end.
%% 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({replace_ns,Ns}) ->
{listreplace,nameservers,Ns};
rc_opt_req({replace_search,Search}) ->
{listreplace,search,Search};
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(clear_ns) ->
[{listreplace,nameservers,[]},{listreplace,alt_nameservers,[]}];
rc_opt_req(clear_search) ->
{listreplace,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(servfail_retry_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 is a cache.
%% If the cache is full this function first deletes old entries,
%% i.e. entries with the oldest 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 ->
%% Add to cache
#dns_rr{
domain = Domain, class = Class, type = Type,
data = Data} = RR,
DeleteRRs =
ets:match_object(
CacheDb, dns_rr_match(Domain, Class, Type, Data)),
InsertRR = RR#dns_rr{tm = TM, cnt = TM},
%% Insert before delete to always have an RR present.
%% Watch out to not delete what we insert.
case lists:member(InsertRR, DeleteRRs) of
true ->
_ = [ets:delete_object(CacheDb, DelRR) ||
DelRR <- DeleteRRs,
DelRR =/= InsertRR],
true;
false ->
ets:insert(CacheDb, InsertRR),
_ = [ets:delete_object(CacheDb, DelRR) ||
DelRR <- DeleteRRs],
true
end;
false ->
false
end.
times() ->
erlang:monotonic_time(second).
%% ETS match expressions
%%
-compile(
{inline,
[dns_rr_match_tm_ttl_cnt/3, dns_rr_match_cnt/1,
dns_rr_match/3, dns_rr_match/4]}).
%%
dns_rr_match_tm_ttl_cnt(TM, TTL, Cnt) ->
#dns_rr{
domain = '_', class = '_', type = '_', data = '_',
cnt = Cnt, tm = TM, ttl = TTL, bm = '_', func = '_'}.
dns_rr_match_cnt(Cnt) ->
#dns_rr{
domain = '_', class = '_', type = '_', data = '_',
cnt = Cnt, tm = '_', ttl = '_', bm = '_', func = '_'}.
%%
dns_rr_match(Domain, Class, Type) ->
#dns_rr{
domain = Domain, class = Class, type = Type, data = '_',
cnt = '_', tm = '_', ttl = '_', bm = '_', func = '_'}.
%%
dns_rr_match(Domain, Class, Type, Data) ->
#dns_rr{
domain = Domain, class = Class, type = Type, data = Data,
cnt = '_', tm = '_', ttl = '_', bm = '_', func = '_'}.
%% RR creation
-compile({inline, [dns_rr_add/5]}).
%%
dns_rr_add(Domain, Class, Type, TTL, Data) ->
#dns_rr{
domain = Domain, class = Class, type = Type,
ttl = TTL, data = Data}.
%% We are simultaneously updating the table from all clients
%% and the server, so we might get duplicate recource records
%% in the table, i.e identical domain, class, type and data.
%% We embrace that and eliminate duplicates here.
%%
%% Look up all matching objects. The still valid ones
%% should be returned, and updated with a new cnt time.
%% All expired ones should be deleted. We count TTL 0
%% RRs as valid but immediately expired.
%%
match_rr(MatchRR) ->
CacheDb = inet_cache,
RRs = ets:match_object(CacheDb, MatchRR),
match_rr(CacheDb, RRs, times(), #{}, #{}, []).
%%
match_rr(CacheDb, [], _Time, ResultRRs, InsertRRs, DeleteRRs) ->
%% We insert first so an RR always is present,
%% which may create duplicates
_ = [ets:insert(CacheDb, RR) || RR <- maps:values(InsertRRs)],
_ = [ets:delete_object(CacheDb, RR) || RR <- DeleteRRs],
maps:values(ResultRRs);
match_rr(CacheDb, [RR | RRs], Time, ResultRRs, InsertRRs, DeleteRRs) ->
%%
#dns_rr{ttl = TTL, tm = TM, cnt = Cnt} = RR,
if
TTL =:= 0 ->
%% Valid, immediately expired; return and delete
Key = match_rr_key(RR),
match_rr(
CacheDb, RRs, Time,
ResultRRs#{Key => RR}, InsertRRs, [RR | DeleteRRs]);
TM + TTL < Time ->
%% Expired, delete
match_rr(
CacheDb, RRs, Time,
ResultRRs, InsertRRs, [RR | DeleteRRs]);
Time =< Cnt ->
%% Valid and just updated, return and do not update
Key = match_rr_key(RR),
match_rr(
CacheDb, RRs, Time,
ResultRRs#{Key => RR}, InsertRRs, DeleteRRs);
true ->
%% Valid; return and re-insert with updated cnt time.
%% The clause above ensures that the cnt field is changed
%% which is essential to not accidentally delete
%% a record we also insert.
Key = match_rr_key(RR),
match_rr(
CacheDb, RRs, Time,
ResultRRs#{Key => RR},
InsertRRs#{Key => RR#dns_rr{cnt = Time}},
[RR | DeleteRRs])
end.
-compile({inline, [match_rr_key/1]}).
match_rr_key(
#dns_rr{domain = Domain, class = Class, type = Type, data = Data}) ->
{Domain, Class, Type, Data}.
%% Lowercase the domain name before storage.
%%
lower_rr(#dns_rr{domain=Domain}=RR) when is_list(Domain) ->
RR#dns_rr { domain = tolower(Domain) };
lower_rr(RR) -> RR.
%%
%% Case fold upper-case to lower-case according to RFC 4343
%% "Domain Name System (DNS) Case Insensitivity Clarification".
%%
%% NOTE: this code is in kernel and we don't want to rely
%% to much on stdlib. Furthermore string:to_lower/1
%% does not follow RFC 4343.
%%
tolower([]) -> [];
tolower([C|Cs]) when is_integer(C) ->
if C >= $A, C =< $Z ->
[(C-$A)+$a|tolower(Cs)];
true ->
[C|tolower(Cs)]
end.
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 number of deleted entries.
%%
delete_expired(CacheDb, TM) ->
ets:select_delete(
CacheDb,
[{dns_rr_match_tm_ttl_cnt('$1', '$2', '_'), [],
%% Delete all with tm + ttl < TM
[{'<', {'+', '$1', '$2'}, {const, TM}}]}]).
%% -------------------------------------------------------------------
%% 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) / 3) + OldestTM
%% from the cache if it is full.
%%
%% Does not delete more than 1/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
%% (true if we have a cache since we always make room for new).
%% -------------------------------------------------------------------
alloc_entry(Db, CacheDb, TM) ->
Size = ets:lookup_element(Db, cache_size, 2),
if
Size =< 0 ->
false;
true ->
CurSize = ets:info(CacheDb, size),
if
Size =< CurSize ->
N = ((Size - 1) div 10) + 1,
_ = delete_oldest(CacheDb, TM, N),
true;
true ->
true
end
end.
%% This deletion should always give some room since
%% it removes a percentage of the oldest entries.
%%
%% Fetch all cnt times, sort them, calculate a limit
%% as the earliest of the time 1/3 from the oldest to now,
%% and the 1/10 oldest entry,.
%%
%% Delete all entries with a cnt time older than that,
%% and all expired (tm + ttl < now).
%%
delete_oldest(CacheDb, TM, N) ->
case
lists:sort(
ets:select(
CacheDb,
%% All cnt vals
[{dns_rr_match_cnt('$1'), [], ['$1']}]))
%% That could be space optimized by using ets:select/3
%% with a limit, and storing the returned times in
%% gb_sets with size limitation of N. Then we would
%% never have to sort the whole list and find
%% the N:th element, but instead take the smallest
%% and largest elements from gb_sets.
%%
%% The size of the whole list is, however, already
%% much smaller than all table entries, so is is
%% unclear how much of an improvement that would be.
%%
%% Note that since gb_sets does not store duplicate
%% times, that will not work nicely if there are
%% many duplicate times, which is not unlikely
%% given the second resolution. Therefore it is
%% possible that gb_trees and storing the number
%% of occurences for a cnt time might be needed,
%% so insertion gets more complicated and slower,
%% and we need our own concept of set size.
%%
of
[] -> % Empty table, this should not happen,
0;
[OldestTM | _] = TMs ->
DelTM_A = ((TM - OldestTM) div 3) + OldestTM,
DelTM_B = lists_nth(N, TMs, DelTM_A), % N:th cnt time
DelTM = min(DelTM_A, DelTM_B),
%%
ets:select_delete(
CacheDb,
[{dns_rr_match_tm_ttl_cnt('$1', '$2', '$3'), [],
%% RRs with cnt =< DelTM or tm + ttl < TM
[{'orelse',
{'=<', '$3', {const, DelTM}},
{'<', {'+', '$1', '$2'}, {const, TM}}}]}])
end.
%% 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)].
%% as lists:nth/2 but return Default for out of bounds
lists_nth(0, List, Default) when is_list(List) ->
Default;
lists_nth(1, [H | _], _Default) ->
H;
lists_nth(_N, [], Default) ->
Default;
lists_nth(N, [_ | T], Default) ->
lists_nth(N - 1, T, Default).
%%----------------------------------------------------------------------
%% Socket related functions
%%----------------------------------------------------------------------
handle_put_socket_type(Db, MRef, Type) ->
Key = {type, MRef},
case ets:lookup(Db, Key) of
[_] -> % "Should" be impossible...
error;
[] ->
ets:insert(Db, {Key, Type}),
ok
end.
handle_take_socket_type(Db, MRef) ->
Key = {type, MRef},
case ets:take(Db, Key) of
[{Key, Type}] ->
{ok, Type};
[] -> % Already demonitor'ed
error
end.