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

openSUSE Build Service is sponsored by