File 3611-Make-a-final-lookup-assuming-the-name-to-look-up-is-.patch of Package erlang
From 223029264f5c6f0f7954a04df551d11505c74ede Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Wed, 21 Jan 2026 15:16:39 +0100
Subject: [PATCH 1/2] Make a final lookup assuming the name to look up is
abolute
This is what all OS resolvers we know of does, and it has been
on a future improvement list since at least R13B01, in a comment
in inet_res.erl.
So, when the name to look up has been tried with all domain
search list entries, do a final attempt as if the top domain
is an invisible last domain search list entry.
This only applies to names without a dot (implicit ndots=1),
since for names with at least one dot they are tried as
absolute names *before* starting with the search list,
as before this change.
---
lib/kernel/src/inet_db.erl | 16 +++-
lib/kernel/src/inet_res.erl | 93 +++++++++++--------
lib/kernel/test/inet_res_SUITE.erl | 62 +++++++++++--
.../inet_res_SUITE_data/otptest/otptest.zone | 6 ++
4 files changed, 124 insertions(+), 53 deletions(-)
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 8d2442e734..bd718635f3 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -3,7 +3,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2025. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2026. 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.
@@ -638,15 +638,23 @@ res_cache_answer(RRs) ->
getbyname(Name, Type) ->
{EmbeddedDots, TrailingDot} = inet_parse:dots(Name),
Dot = if TrailingDot -> ""; true -> "." end,
- if TrailingDot ->
+ if
+ TrailingDot ->
hostent_by_domain(Name, Type);
EmbeddedDots =:= 0 ->
- getbysearch(Name, Dot, get_searchlist(), Type, {error,nxdomain});
+ case
+ getbysearch(
+ Name, Dot, get_searchlist(), Type, {error,nxdomain})
+ of
+ {error,_} ->
+ hostent_by_domain(Name, Type);
+ Other1 -> Other1
+ end;
true ->
case hostent_by_domain(Name, Type) of
{error,_}=Error ->
getbysearch(Name, Dot, get_searchlist(), Type, Error);
- Other -> Other
+ Other2 -> Other2
end
end.
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 1e8eb36113..6ed4182f92 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -3,7 +3,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2024. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2026. 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.
@@ -881,35 +881,36 @@ type_p(Type) ->
%%
%% New behaviour (this code), honoring the old behaviour but
%% doing better for absolute names:
-%% * For Name = "foo" try "foo.dom1", "foo.dom2" at normal nameservers
+%% * For Name = "foo" try "foo.dom1", "foo.dom2" at normal nameservers,
+%% then "foo" at normal then alt. nameservers
%% * For Name = "foo.bar" try "foo.bar" at normal then alt. nameservers
%% then try "foo.bar.dom1", "foo.bar.dom2"
%% at normal nameservers
%% * For Name = "foo." try "foo" at normal then alt. nameservers
%% * For Name = "foo.bar." try "foo.bar" at normal then alt. nameservers
%%
-%%
-%% FIXME This is probably how it should be done:
-%% Common behaviour (Solaris resolver) is:
-%% * For Name = "foo." try "foo"
-%% * For Name = "foo.bar." try "foo.bar"
-%% * For Name = "foo" try "foo.dom1", "foo.dom2", "foo"
-%% * For Name = "foo.bar" try "foo.bar.dom1", "foo.bar.dom2", "foo.bar"
-%% That is to try Name as it is as a last resort if it is not absolute.
-%%
res_getbyname(Name, Type, Options, Timer) ->
{EmbeddedDots, TrailingDot} = inet_parse:dots(Name),
if
TrailingDot ->
res_getby_query(lists:droplast(Name), Type, Options, Timer);
EmbeddedDots =:= 0 ->
- res_getby_search(Name, inet_db:get_searchlist(),
- nxdomain, Type, Options, Timer);
+ case
+ res_getby_search(
+ Name, inet_db:get_searchlist(),
+ nxdomain, Type, Options, Timer)
+ of
+ {error, _Reason} ->
+ res_getby_query(Name, Type, Options, Timer);
+ Other ->
+ Other
+ end;
true ->
case res_getby_query(Name, Type, Options, Timer) of
- {error,_Reason}=Error ->
- res_getby_search(Name, inet_db:get_searchlist(),
- Error, Type, Options, Timer);
+ {error, Reason} ->
+ res_getby_search(
+ Name, inet_db:get_searchlist(),
+ Reason, Type, Options, Timer);
Other -> Other
end
end.
@@ -966,31 +967,40 @@ res_getby_query(Name, Type, Options, Timer, NSs) ->
-%% Query first nameservers list then alt_nameservers list
-res_query(Name, Class, Type, Options, Timer) ->
- Q = make_query(Name, Class, Type, Options),
- case do_query(Q, Options#options.nameservers, Timer) of
- {error,nxdomain}=Error ->
- res_query_alt(Q, Error, Timer);
- {error,{nxdomain,_}}=Error ->
- res_query_alt(Q, Error, Timer);
- {ok,#dns_rec{anlist=[]}}=Reply ->
- res_query_alt(Q, Reply, Timer);
- Reply -> Reply
- end.
-
%% Query just the argument nameservers list
res_query(Name, Class, Type, Options, Timer, NSs) ->
- Q = make_query(Name, Class, Type, Options),
- do_query(Q, NSs, Timer).
-
-res_query_alt(#q{options=#options{alt_nameservers=NSs}}=Q, Reply, Timer) ->
case NSs of
- [] -> Reply;
- _ ->
- do_query(Q, NSs, Timer)
+ [] ->
+ {error,nxdomain};
+ [_|_] ->
+ Q = make_query(Name, Class, Type, Options),
+ do_query(Q, NSs, Timer)
end.
+%% Query first nameservers list then alt_nameservers list;
+res_query(Name, Class, Type, Options, Timer) ->
+ case Options of
+ #options{nameservers = [], alt_nameservers = []} ->
+ {error,nxdomain};
+ #options{
+ nameservers = Nameservers,
+ alt_nameservers = AltNameservers} ->
+ Q = make_query(Name, Class, Type, Options),
+ case res_query(Q, Nameservers, {error,nxdomain}, Timer) of
+ {error,nxdomain} = Error ->
+ res_query(Q, AltNameservers, Error, Timer);
+ {error,{nxdomain,_}} = Error ->
+ res_query(Q, AltNameservers, Error, Timer);
+ {ok,#dns_rec{anlist=[]}} = Reply ->
+ res_query(Q, AltNameservers, Reply, Timer);
+ Reply -> Reply
+ end
+ end.
+
+res_query(_Q, [], Result, _Timer) -> Result;
+res_query(Q, NSs, _Result, Timer) -> do_query(Q, NSs, Timer).
+
+
make_query(Dname, Class, Type, Options) ->
case Options#options.edns of
false ->
@@ -1145,9 +1155,6 @@ udp_close(#sock{inet=I,inet6=I6}) ->
%%
%% And that is what the code seems to do, now fixed, hopefully...
-do_query(_Q, [], _Timer) ->
- %% We have no name server to ask, so say nxdomain
- {error,nxdomain};
do_query(#q{options=#options{retry=Retry}}=Q, NSs, Timer) ->
%% We have at least one name server,
%% so a failure will be a time-out,
@@ -1354,7 +1361,9 @@ query_ns(S0, {Msg, Buffer}, IP, Port, Timer, Retry, I,
end
end.
-query_udp(_S, _Msg, _Buffer, _IP, _Port, 0, _Verbose) ->
+query_udp(_S, _Msg, _Buffer, IP, Port, 0, Verbose) ->
+ ?verbose(Verbose, "No try UDP server : ~p:~p (overdue)\n",
+ [IP,Port]),
timeout;
query_udp(S, Msg, Buffer, IP, Port, Timeout, Verbose) ->
?verbose(Verbose, "Try UDP server : ~p:~p (timeout=~w)\n",
@@ -1390,7 +1399,9 @@ query_udp(S, Msg, Buffer, IP, Port, Timeout, Verbose) ->
{error,econnrefused}
end.
-query_tcp(0, _Msg, _Buffer, _IP, _Port, _Verbose) ->
+query_tcp(0, _Msg, _Buffer, IP, Port, Verbose) ->
+ ?verbose(Verbose, "No try TCP server : ~p:~p (overdue)\n",
+ [IP, Port]),
timeout;
query_tcp(Timeout, Msg, Buffer, IP, Port, Verbose) ->
?verbose(Verbose, "Try TCP server : ~p:~p (timeout=~w)\n",
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index a08e255fe5..0125dfb42c 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -3,7 +3,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2024. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2026. 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.
@@ -630,6 +630,8 @@ basic(Config) when is_list(Config) ->
{ok,#hostent{h_addr_list=IPs2}} =
inet_res:gethostbyname(NameC, inet, Options, infinity),
[IP1, IP2] = lists:sort(IPs2),
+ {ok,#hostent{h_addr_list=[{127,0,0,0}]}} =
+ inet_res:gethostbyname("otptest", inet, Options, infinity),
%%
%% getbyname
?P("getbyname"),
@@ -1193,12 +1195,17 @@ txt_record(Config) when is_list(Config) ->
%% Tests monitoring of /etc/hosts and /etc/resolv.conf, but not them.
files_monitor(Config) when is_list(Config) ->
?P("begin"),
+ Nameservers = inet_db:res_option(nameservers),
+ AltNameservers = inet_db:res_option(alt_nameservers),
Search = inet_db:res_option(search),
HostsFile = inet_db:res_option(hosts_file),
ResolvConf = inet_db:res_option(resolv_conf),
Inet6 = inet_db:res_option(inet6),
- try do_files_monitor(Config)
+ {_,Ns,_} = proplists:get_value(nameserver, Config),
+ try do_files_monitor(Config, Ns)
after
+ inet_db:res_option(nameservers, Nameservers),
+ inet_db:res_option(alt_nameservers, AltNameservers),
inet_db:res_option(search, Search),
inet_db:res_option(resolv_conf, ResolvConf),
inet_db:res_option(hosts_file, HostsFile),
@@ -1207,7 +1214,7 @@ files_monitor(Config) when is_list(Config) ->
?P("done"),
ok.
-do_files_monitor(Config) ->
+do_files_monitor(Config, Ns = {NsIP,NsPort}) ->
Dir = proplists:get_value(priv_dir, Config),
{ok,Hostname} = inet:gethostname(),
?P("Hostname: ~p", [Hostname]),
@@ -1223,6 +1230,9 @@ do_files_monitor(Config) ->
ResolvConf = filename:join(Dir, "files_monitor_resolv.conf"),
ok = inet_db:res_option(resolv_conf, ResolvConf),
ok = inet_db:res_option(hosts_file, HostsFile),
+ ok = inet_db:res_option(nameservers, [Ns]),
+ [Ns] = inet_db:res_option(nameservers),
+ ok = inet_db:res_option(alt_nameservers, []),
[] = inet_db:res_option(search),
%% The inet function will use its final fallback to find this host
{ok,#hostent{h_name = Hostname,
@@ -1233,8 +1243,8 @@ do_files_monitor(Config) ->
h_addrtype = inet,
h_length = 4,
h_addr_list = [{127,0,0,1}]}} = inet:gethostbyname(FQDN),
- {error,nxdomain} = inet_res:gethostbyname(Hostname),
- {error,nxdomain} = inet_res:gethostbyname(FQDN),
+ {error,_} = inet_res:gethostbyname(Hostname),
+ {error,_} = inet_res:gethostbyname(FQDN),
{ok,{127,0,0,10}} = inet:getaddr("mx.otptest", inet),
{ok,{0,0,0,0,0,0,32512,28}} = inet:getaddr("resolve.otptest", inet6),
%% The inet function will use its final fallback to find this host
@@ -1248,19 +1258,43 @@ do_files_monitor(Config) ->
h_length = 16,
h_addr_list = [{0,0,0,0,0,0,0,1}]}} =
inet:gethostbyname(FQDN, inet6),
- {error,nxdomain} = inet_res:gethostbyname("resolve"),
- %% XXX inet does not honour res_option inet6, might be a problem?
- %% therefore inet_res is called here
+ {error,_} = inet_res:gethostbyname("resolve"),
ok = inet_db:res_option(inet6, true),
{ok,#hostent{h_name = "resolve.otptest",
h_addrtype = inet6,
h_length = 16,
h_addr_list = [{0,0,0,0,0,0,32512,28}]}} =
inet_res:gethostbyname("resolve.otptest"),
+ %% The search list is empty so "otptest" will be tried
+ %% as an absolute name, and resolves to the A record
+ %% for the "otptest" domain.
+ {ok,#hostent{h_name = "otptest",
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = [{0,0,0,0,0,0,32512,0}]}} =
+ inet_res:gethostbyname("otptest"),
+ {ok,#hostent{h_name = "otptest.otptest",
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = [{0,0,0,0,0,0,32512,29}]}} =
+ inet_res:gethostbyname("otptest.otptest"),
{error,nxdomain} = inet_hosts:gethostbyname("files_monitor"),
ok = file:write_file(ResolvConf, "search otptest\n"),
ok = file:write_file(HostsFile, "::100 files_monitor\n"),
receive after 7000 -> ok end, % RES_FILE_UPDATE_TM in inet_res.hrl is 5 s
+ %% The following lookup will trigger a resolv.conf file read,
+ %% but the file contains no name servers, so inet_res
+ %% will return nxdomain, and inet will fall back to gethostbyname_self.
+ {ok,#hostent{h_name = Hostname,
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = [{0,0,0,0,0,0,0,1}]}} =
+ inet:gethostbyname(Hostname),
+ %% We cannot add a name server with port number through
+ %% resolv.conf, so we will have to add it manually here,
+ %% after the file has been read
+ [] = inet_db:res_option(nameservers),
+ ok = inet_db:add_ns(NsIP, NsPort),
{ok,#hostent{h_name = "resolve.otptest",
h_addrtype = inet6,
h_length = 16,
@@ -1278,6 +1312,18 @@ do_files_monitor(Config) ->
h_length = 4,
h_addr_list = [{127,0,0,28}]}} =
inet:gethostbyname("resolve.otptest"),
+ %% Now the search list contains "otptest", so the short name
+ %% "otptest" will be tried as "otptest.otptest"v
+ {ok,#hostent{h_name = "otptest.otptest",
+ h_addrtype = inet,
+ h_length = 4,
+ h_addr_list = [{127,0,0,29}]}} =
+ inet_res:gethostbyname("otptest", inet, [verbose], infinity),
+ {ok,#hostent{h_name = "otptest.otptest",
+ h_addrtype = inet,
+ h_length = 4,
+ h_addr_list = [{127,0,0,29}]}} =
+ inet_res:gethostbyname("otptest.otptest"),
ok.
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone b/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
index dc004896cb..f6ca118e0b 100644
--- a/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
+++ b/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
@@ -17,6 +17,8 @@ $TTL 3600
IN MX 20 mx5-5678901234567890123456789012345678
IN MX 20 mx6-5678901234567890123456789012345678
IN MX 20 mx7-5678901234567890123456789012345678
+ IN A 127.0.0.0
+ IN AAAA ::127.0.0.0
test1-78901234567890123456789012345678 IN A 127.0.0.1
test2-78901234567890123456789012345678 IN A 127.0.0.2
@@ -38,6 +40,9 @@ ns IN AAAA ::127.0.0.253
resolve IN A 127.0.0.28
resolve IN AAAA ::127.0.0.28
cname.resolve IN CNAME resolve
+otptest IN A 127.0.0.29
+otptest IN AAAA ::127.0.0.29
+
;wks.resolve IN WKS 127.0.0.28 TCP ( TELNET SMTP )
wks.resolve IN TYPE11 \# 9 7f00001c 06 00000140 ; ... (23 25)
resolve IN HINFO "BEAM" "Erlang/OTP"
@@ -62,6 +67,7 @@ uri.resolve IN TYPE256 \# 21 000a 0003 (68747470 3a2f2f 65726c616e67 2e 6f72
;caa.resolve IN CAA 1 iodef "http://iodef.erlang.org"
caa.resolve IN TYPE257 \# 30 01 (05 696f646566) (68747470 3a2f2f 696f646566 2e 65726c616e67 2e 6f7267)
+
; for testing TSIG we need a multi-message response so we use AXFR but need
; to juice up the zone file to meet our needs, the following creates 3 messages
;
--
2.51.0