File 7351-Add-inet_dns-decode-2-and-inet_dns-encode-2.patch of Package erlang

From 1843057b974e5a856024923a8405e9ce27399624 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Fri, 24 Nov 2023 14:00:24 +0100
Subject: [PATCH] Add `inet_dns:decode/2` and `inet_dns:encode/2`

Add new functions that take an option to indicate
if encode/decode should be for mDNS, since it affects
the value range for the CLASS to 15 or 16 bits.
---
 lib/kernel/src/inet_dns.erl            | 167 ++++++++++++++-----------
 lib/kernel/src/inet_dns_record_adts.pl |   8 +-
 lib/kernel/src/inet_res.erl            |   4 +-
 lib/kernel/test/inet_res_SUITE.erl     |  81 +++++++++++-
 4 files changed, 180 insertions(+), 80 deletions(-)

diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl
index c65546a7e5..94598905be 100644
--- a/lib/kernel/src/inet_dns.erl
+++ b/lib/kernel/src/inet_dns.erl
@@ -35,7 +35,7 @@
 %% RFC 7553: The Uniform Resource Identifier (URI) DNS Resource Record
 %% RFC 8945: Secret Key Transaction Authentication for DNS (TSIG)
 
--export([decode/1, encode/1]).
+-export([decode/1, decode/2, encode/1, encode/2]).
 -export([decode_algname/1, encode_algname/1]).
 
 -import(lists, [reverse/1]).
@@ -148,8 +148,10 @@ lists_member(H, [_|T]) -> lists_member(H, T).
            throw(?DECODE_ERROR)
    end).
 
-decode(Buffer) when is_binary(Buffer) ->
-    try do_decode(Buffer) of
+decode(Buffer) -> decode(Buffer, true). % Backwards compatible
+%%
+decode(Buffer, Mdns) when is_binary(Buffer), is_boolean(Mdns) ->
+    try do_decode(Buffer, Mdns) of
 	DnsRec ->
 	    {ok,DnsRec}
     catch
@@ -161,11 +163,14 @@ do_decode(<<Id:16,
 	   QR:1,Opcode:4,AA:1,TC:1,RD:1,
 	   RA:1,PR:1,_:2,Rcode:4,
 	   QdCount:16,AnCount:16,NsCount:16,ArCount:16,
-	   QdBuf/binary>>=Buffer) ->
-    {AnBuf,QdList,QdTC} = decode_query_section(QdBuf,QdCount,Buffer),
-    {NsBuf,AnList,AnTC} = decode_rr_section(Opcode,AnBuf,AnCount,Buffer),
-    {ArBuf,NsList,NsTC} = decode_rr_section(Opcode,NsBuf,NsCount,Buffer),
-    {Rest,ArList,ArTC} = decode_rr_section(Opcode,ArBuf,ArCount,Buffer),
+	   QdBuf/binary>>=Buffer, Mdns) ->
+    {AnBuf,QdList,QdTC} = decode_query_section(QdBuf,QdCount,Buffer,Mdns),
+    {NsBuf,AnList,AnTC} =
+        decode_rr_section(AnBuf,AnCount,Buffer,{Opcode,Mdns}),
+    {ArBuf,NsList,NsTC} =
+        decode_rr_section(NsBuf,NsCount,Buffer,{Opcode,Mdns}),
+    {Rest,ArList,ArTC} =
+        decode_rr_section(ArBuf,ArCount,Buffer,{Opcode,Mdns}),
     ?MATCH_ELSE_DECODE_ERROR(
        Rest,
        <<>>,
@@ -197,40 +202,40 @@ do_decode(<<Id:16,
                            arlist=ArList}
               end)
        end);
-do_decode(_) ->
+do_decode(_, _) ->
     %% DNS message does not even match header
     throw(?DECODE_ERROR).
 
-decode_query_section(Bin, N, Buffer) ->
-    decode_query_section(Bin, N, Buffer, []).
+decode_query_section(Bin, N, Buffer, Mdns) ->
+    decode_query_section(Bin, N, Buffer, Mdns, []).
 
-decode_query_section(<<>>=Rest, N, _Buffer, Qs) ->
+decode_query_section(<<>>=Rest, N, _Buffer, _Mdns, Qs) ->
     {Rest,reverse(Qs),N =/= 0};
-decode_query_section(Rest, 0, _Buffer, Qs) ->
+decode_query_section(Rest, 0, _Buffer, _Mdns, Qs) ->
     {Rest,reverse(Qs),false};
-decode_query_section(Bin, N, Buffer, Qs) ->
+decode_query_section(Bin, N, Buffer, Mdns, Qs) ->
     ?MATCH_ELSE_DECODE_ERROR(
        decode_name(Bin, Buffer),
        {<<T:16,C:16,Rest/binary>>,Name},
        begin
-           {Class,UnicastResponse} = decode_class(C),
+           {Class,UnicastResponse} = decode_class(C, Mdns),
            DnsQuery =
                #dns_query{
                   domain           = Name,
                   type             = decode_type(T),
                   class            = Class,
                   unicast_response = UnicastResponse},
-           decode_query_section(Rest, N-1, Buffer, [DnsQuery|Qs])
+           decode_query_section(Rest, N-1, Buffer, Mdns, [DnsQuery|Qs])
        end).
 
-decode_rr_section(Opcode, Bin, N, Buffer) ->
-    decode_rr_section(Opcode, Bin, N, Buffer, []).
-
-decode_rr_section(_Opcode, <<>>=Rest, N, _Buffer, RRs) ->
+decode_rr_section(Bin, N, Buffer, Opts) ->
+    decode_rr_section(Bin, N, Buffer, Opts, []).
+%%
+decode_rr_section(<<>>=Rest, N, _Buffer, _Opts, RRs) ->
     {Rest,reverse(RRs),N =/= 0};
-decode_rr_section(_Opcode, Rest, 0, _Buffer, RRs) ->
+decode_rr_section(Rest, 0, _Buffer, _Opts, RRs) ->
     {Rest,reverse(RRs),false};
-decode_rr_section(Opcode, Bin, N, Buffer, RRs) ->
+decode_rr_section(Bin, N, Buffer, {Opcode,Mdns} = Opts, RRs) ->
     ?MATCH_ELSE_DECODE_ERROR(
        decode_name(Bin, Buffer),
        {<<T:16/unsigned,C:16/unsigned,TTL:4/binary,
@@ -278,14 +283,8 @@ decode_rr_section(Opcode, Bin, N, Buffer, RRs) ->
                              error         = Error,
                              other_data    = OtherData});
                    _ ->
-                       {Class,CacheFlush} = decode_class(C),
-                       Data = if
-                           %% RFC 2136: 2.4. Allow length zero data for UPDATE
-                           Opcode == ?UPDATE, D == <<>> ->
-                               #dns_rr{}#dns_rr.data;
-                           true ->
-                               decode_data(D, Class, Type, Buffer)
-                       end,
+                       {Class,CacheFlush} = decode_class(C, Mdns),
+                       Data = decode_data(D, Class, Type, Buffer, Opcode),
                        <<TimeToLive:32/signed>> = TTL,
                        #dns_rr{
                           domain = Name,
@@ -295,25 +294,31 @@ decode_rr_section(Opcode, Bin, N, Buffer, RRs) ->
                           data   = Data,
                           func   = CacheFlush}
                end,
-           decode_rr_section(Opcode, Rest, N-1, Buffer, [RR|RRs])
+           decode_rr_section(Rest, N-1, Buffer, Opts, [RR|RRs])
        end).
 
 %%
 %% Encode a user query
 %%
 
-encode(Q) ->
-    QdCount = length(Q#dns_rec.qdlist),
-    AnCount = length(Q#dns_rec.anlist),
-    NsCount = length(Q#dns_rec.nslist),
-    ArCount = length(Q#dns_rec.arlist),
-    OC = Q#dns_rec.header#dns_header.opcode,
-    B0 = encode_header(Q#dns_rec.header, QdCount, AnCount, NsCount, ArCount),
+encode(Q) -> encode(Q, true). % Backwards compatible
+%%
+encode(
+  #dns_rec{
+     header = Header,
+     qdlist = QdList, anlist = AnList, nslist = NsList, arlist = ArList },
+  Mdns)
+  when is_boolean(Mdns) ->
+    B0 =
+        encode_header(
+          Header,
+          length(QdList), length(AnList), length(NsList), length(ArList)),
+    Opcode = Header#dns_header.opcode,
     C0 = gb_trees:empty(),
-    {B1,C1} = encode_query_section(B0, C0, Q#dns_rec.qdlist),
-    {B2,C2} = encode_res_section(OC, B1, C1, Q#dns_rec.anlist),
-    {B3,C3} = encode_res_section(OC, B2, C2, Q#dns_rec.nslist),
-    {B,_} = encode_res_section(OC, B3, C3, Q#dns_rec.arlist),
+    {B1,C1} = encode_query_section(B0, Mdns, C0, QdList),
+    {B2,C2} = encode_res_section(B1, {Opcode,Mdns}, C1, AnList),
+    {B3,C3} = encode_res_section(B2, {Opcode,Mdns}, C2, NsList),
+    {B,_} = encode_res_section(B3, {Opcode,Mdns}, C3, ArList),
     B.
 
 
@@ -335,19 +340,20 @@ encode_header(#dns_header{id=Id}=H, QdCount, AnCount, NsCount, ArCount) ->
 
 %% RFC 1035: 4.1.2. Question section format
 %%
-encode_query_section(Bin, Comp, []) -> {Bin,Comp};
-encode_query_section(Bin0, Comp0, [#dns_query{domain=DName}=Q | Qs]) ->
+encode_query_section(Bin, _Mdns, Comp, []) -> {Bin,Comp};
+encode_query_section(Bin0, Mdns, Comp0, [#dns_query{domain=DName}=Q | Qs]) ->
     T = encode_type(Q#dns_query.type),
-    C = encode_class(Q#dns_query.class, Q#dns_query.unicast_response),
+    C = encode_class(
+          Q#dns_query.class, Mdns andalso Q#dns_query.unicast_response),
     {Bin,Comp} = encode_name(Bin0, Comp0, byte_size(Bin0), DName),
-    encode_query_section(<<Bin/binary,T:16,C:16>>, Comp, Qs).
+    encode_query_section(<<Bin/binary,T:16,C:16>>, Mdns, Comp, Qs).
 
 %% RFC 1035:  4.1.3.               Resource record format
 %% RFC 6891:  6.1.2, 6.1.3, 6.2.3  Opt RR format
 %%
-encode_res_section(_Opcode, Bin, Comp, []) -> {Bin,Comp};
+encode_res_section(Bin, _Opts, Comp, []) -> {Bin,Comp};
 encode_res_section(
-  Opcode, Bin, Comp,
+  Bin, Opts, Comp,
   [#dns_rr{
       domain = DName,
       type   = Type,
@@ -356,10 +362,10 @@ encode_res_section(
       ttl    = TTL,
       data   = Data} | Rs]) ->
     encode_res_section_rr(
-      Opcode, Bin, Comp, Rs, DName, Type, Class, CacheFlush,
+      Bin, Opts, Comp, Rs, DName, Type, Class, CacheFlush,
       <<TTL:32/signed>>, Data);
 encode_res_section(
-  Opcode, Bin, Comp,
+  Bin, Opts, Comp,
   [#dns_rr_opt{
       domain           = DName,
       udp_payload_size = UdpPayloadSize,
@@ -370,10 +376,10 @@ encode_res_section(
       do               = DnssecOk} | Rs]) ->
     DO = case DnssecOk of true -> 1; false -> 0 end,
     encode_res_section_rr(
-      Opcode, Bin, Comp, Rs, DName, ?S_OPT, UdpPayloadSize, false,
+      Bin, Opts, Comp, Rs, DName, ?S_OPT, UdpPayloadSize, false,
       <<ExtRCode,Version,DO:1,Z:15>>, Data);
 encode_res_section(
-  Opcode, Bin, Comp,
+  Bin, Opts, Comp,
   [#dns_rr_tsig{
       domain           = DName,
       algname          = AlgName,
@@ -385,26 +391,21 @@ encode_res_section(
       other_data       = OtherData}]) ->
     Data = {AlgName,Now,Fudge,MAC,OriginalId,Error,OtherData},
     encode_res_section_rr(
-      Opcode, Bin, Comp, [], DName, ?S_TSIG, ?S_ANY, false,
+      Bin, Opts, Comp, [], DName, ?S_TSIG, ?S_ANY, false,
       <<0:32/signed>>, Data).
 
 encode_res_section_rr(
-  Opcode, Bin0, Comp0, Rs, DName, Type, Class, CacheFlush, TTL, Data) ->
+  Bin0, {Opcode,Mdns} = Opts, Comp0, Rs,
+  DName, Type, Class, CacheFlush, TTL, Data) ->
     T = encode_type(Type),
-    C = encode_class(Class, CacheFlush),
+    C = encode_class(Class, Mdns and CacheFlush),
     {Bin,Comp1} = encode_name(Bin0, Comp0, byte_size(Bin0), DName),
     Pos = byte_size(Bin)+(2+2)+byte_size(TTL)+2,
-    {DataBin,Comp} = if
-        Opcode == update, Data == #dns_rr{}#dns_rr.data ->
-            {<<>>,Comp1};
-        true ->
-            encode_data(Comp1, Pos, Type, Class, Data)
-    end,
+    {DataBin,Comp} = encode_data(Comp1, Pos, Type, Class, Data, Opcode),
     DataSize = byte_size(DataBin),
     encode_res_section(
-      Opcode,
       <<Bin/binary,T:16,C:16,TTL/binary,DataSize:16,DataBin/binary>>,
-      Comp, Rs).
+      Opts, Comp, Rs).
 
 %%
 %% Resource types
@@ -497,21 +498,25 @@ encode_type(Type) ->
 %% Resource classes
 %%
 
-decode_class(C0) ->
+decode_class(C, false) ->
+    {decode_class(C),false};
+decode_class(C0, true) ->
     FlagBit = 16#8000,
     C = C0 band (bnot FlagBit),
-    Class =
-        case C of
-            ?C_IN    -> in;
-            ?C_CHAOS -> chaos;
-            ?C_HS    -> hs;
-            ?C_NONE  -> none;
-            ?C_ANY   -> any;
-            _ -> C    %% raw unknown class
-        end,
+    Class = decode_class(C),
     Flag = (C0 band FlagBit) =/= 0,
     {Class,Flag}.
 
+decode_class(C) ->
+    case C of
+        ?C_IN    -> in;
+        ?C_CHAOS -> chaos;
+        ?C_HS    -> hs;
+        ?C_NONE  -> none;
+        ?C_ANY   -> any;
+        _ -> C    %% raw unknown class
+    end.
+
 
 encode_class(Class, Flag) ->
     C = encode_class(Class),
@@ -558,7 +563,14 @@ encode_boolean(B) when is_integer(B) -> B.
 decode_boolean(0) -> false;
 decode_boolean(I) when is_integer(I) -> true.
 
-
+decode_data(Data, Class, Type, Buffer, Opcode) ->
+    if
+        %% RFC 2136: 2.4. Allow length zero data for UPDATE
+        Opcode == ?UPDATE, Data == <<>> ->
+            #dns_rr{}#dns_rr.data;
+        true ->
+            decode_data(Data, Class, Type, Buffer)
+    end.
 %%
 %% Data field -> term() content representation
 %%
@@ -771,6 +783,13 @@ decode_name_label(Label, Name, N) ->
 	    erlang:error(badarg, [Label,Name,N])
     end.
 
+encode_data(Comp, Pos, Type, Class, Data, Opcode) ->
+    if
+        Opcode == update, Data == #dns_rr{}#dns_rr.data ->
+            {<<>>,Comp};
+        true ->
+            encode_data(Comp, Pos, Type, Class, Data)
+    end.
 %%
 %% Data field -> {binary(),NewCompressionTable}
 %%
diff --git a/lib/kernel/src/inet_dns_record_adts.pl b/lib/kernel/src/inet_dns_record_adts.pl
index f3331222fc..0adc2285b9 100644
--- a/lib/kernel/src/inet_dns_record_adts.pl
+++ b/lib/kernel/src/inet_dns_record_adts.pl
@@ -24,16 +24,18 @@ use strict;
 # for internal records.
 # 
 # The following defines which ADT function sets that will be generated
-# and which record fields that will be exponated.
+# and which record fields that will be exposed.
 #
 # (FunctionBaseName => [RecordName, FieldName ...], ...)
 my %Names = ('msg' => ['dns_rec', 'header', 'qdlist',
 		       'anlist', 'nslist', 'arlist'],
-	     'dns_rr' => ['dns_rr', 'domain', 'type', 'class', 'ttl', 'data'],
+	     'dns_rr' => ['dns_rr', 'domain', 'type', 'class', 'ttl', 'data',
+                          'func'],
 	     'dns_rr_opt' => ['dns_rr_opt', 'domain', 'type',
 			      'udp_payload_size', 'ext_rcode', 'version',
 			      'z', 'data', 'do'],
-	     'dns_query' => ['dns_query', 'domain', 'type', 'class'],
+	     'dns_query' => ['dns_query', 'domain', 'type', 'class',
+                             'unicast_response'],
 	     'header' => ['dns_header', 'id', 'qr', 'opcode', 'aa', 'tc',
 			  'rd', 'ra', 'pr', 'rcode']);
 # The functions are defined in the __DATA__ section at the end.
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 091ebef91c..c2b5b3be77 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -691,7 +691,7 @@ make_query(Dname, Class, Type, Options, Edns) ->
                                       class=Class}],
 		   arlist=ARList},
     ?verbose(Options#options.verbose, "Query: ~p~n", [dns_msg(Msg)]),
-    Buffer = inet_dns:encode(Msg),
+    Buffer = inet_dns:encode(Msg, false),
     {Msg, Buffer}.
 
 %% --------------------------------------------------------------------------
@@ -1091,7 +1091,7 @@ query_tcp(Timeout, Msg, Buffer, IP, Port, Verbose) ->
     end.
 
 decode_answer(Answer, Q_Msg, Verbose) ->
-    case inet_dns:decode(Answer) of
+    case inet_dns:decode(Answer, false) of
 	{ok, #dns_rec{header = H, arlist = ARList} = Msg} ->
 	    ?verbose(Verbose, "Got reply: ~p~n", [dns_msg(Msg)]),
 	    T = case lists:keyfind(dns_rr_tsig, 1, ARList) of
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index 045fdcc83b..3a0f2bd05a 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -35,7 +35,8 @@
          edns0/1, edns0_multi_formerr/1, txt_record/1, files_monitor/1,
 	 nxdomain_reply/1, last_ms_answer/1, intermediate_error/1,
          servfail_retry_timeout_default/1, servfail_retry_timeout_1000/1,
-         label_compression_limit/1, update/1, tsig_client/1, tsig_server/1
+         label_compression_limit/1, update/1, tsig_client/1, tsig_server/1,
+         mdns_encode_decode/1
         ]).
 -export([
 	 gethostbyaddr/0, gethostbyaddr/1,
@@ -78,6 +79,7 @@ all() ->
      intermediate_error,
      servfail_retry_timeout_default, servfail_retry_timeout_1000,
      label_compression_limit, update, tsig_client, tsig_server,
+     mdns_encode_decode,
      gethostbyaddr, gethostbyaddr_v6, gethostbyname,
      gethostbyname_v6, getaddr, getaddr_v6, ipv4_to_ipv6,
      host_and_addr].
@@ -1661,6 +1663,83 @@ tsig_server(Domain, TS0, Sock) ->
     ok = gen_tcp:send(Sock, PktR3S).
 
 
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% inet_dns encode/decode specials
+%% Should maybe be a suite of its own.
+
+mdns_encode_decode(Config) when is_list(Config) ->
+    Id = 4711,
+    Opcode = 'query',
+    Class = in,
+    Type = txt,
+    Domain = "test.local",
+    Text = ["abc", "123"],
+    BinText = <<3,$a,$b,$c, 3,$1,$2,$3>>,  % Wire format for Text
+    IN_h = 32769,                          % Class IN with high bit set
+    %%
+    %% Create a unicast-response to a mDNS query
+    %% with cache-flush bit on the RR record (func field),
+    %% which sets the class fields high bit when encoded
+    Header =
+        inet_dns:make_header(
+          [{id, Id}, {qr, true}, {opcode, Opcode},
+           {aa, false}, {tc, false}, {rd, false}, {ra, false}, {pr, false}]),
+    Query =
+        inet_dns:make_dns_query(
+          [{class, Class}, {type, Type}, {domain, Domain},
+           {unicast_response, true}]), % High bit 1
+    TxtRR =
+        inet_dns:make_rr(
+          [{domain, Domain}, {class, Class}, {type, Type},
+           {data, Text},
+           {func, true}]), % High bit 1
+    Msg =
+        inet_dns:make_msg(
+          [{header, Header}, {qdlist, [Query]}, {anlist, [TxtRR]}]),
+    %%
+    %% Encode and verify decode
+    Buffer = inet_dns:encode(Msg),
+    {{ok, Msg}, Msg} = {inet_dns:decode(Buffer), Msg},
+    %%
+    %% Decode as if not mDNS, which exposes the high class field bit,
+    %% and doesn't decode the RR data
+    Query2 =
+        inet_dns:make_dns_query(
+          Query,
+          [{class,IN_h},
+           {unicast_response,false}]),  % High bit 0
+    TxtRR2 =
+        inet_dns:make_rr(
+          TxtRR,
+          [{class,IN_h},
+           {data,BinText},  % Raw, encoded
+           {func,false}]),  % High bit 0
+    Msg2 = inet_dns:make_msg(Msg, [{qdlist, [Query2]}, {anlist, [TxtRR2]}]),
+    {{ok, Msg2}, Msg2} = {inet_dns:decode(Buffer, false), Msg2},
+    %%
+    %% Encode non-mDNS which ignores the high class bit flags
+    %% in #dns_query.unicast_response and #dns_rr.func
+    Buffer3 = inet_dns:encode(Msg, false),
+    %%
+    %% Decode non-mDNS and verify
+    Query3 =
+        inet_dns:make_dns_query(Query, unicast_response, false), % High bit 0
+    TxtRR3 = inet_dns:make_rr(TxtRR, func, false),               % High bit 0
+    Msg3 = inet_dns:make_msg(Msg, [{qdlist, [Query3]}, {anlist, [TxtRR3]}]),
+    {{ok, Msg3}, Msg3} = {inet_dns:decode(Buffer3, false), Msg3},
+    %%
+    %% Decode mDNS should give the same answer since the high bits
+    %% in the class fields are encoded as zero
+    {{ok, Msg3}, Msg3} = {inet_dns:decode(Buffer3, true), Msg3},
+    %%
+    %% Encode non-mDNS with class >= 32768
+    Buffer4 = inet_dns:encode(Msg2, false),
+    {{ok, Msg2}, Msg2} = {inet_dns:decode(Buffer4, false), Msg2},
+    %%
+    %% Decoding for mDNS should set the high class field flags
+    {{ok, Msg}, Msg} = {inet_dns:decode(Buffer4, true), Msg},
+    ok.
+
 %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Compatibility tests. Call the inet_SUITE tests, but with
 %% lookup = [file,dns] instead of [native]
-- 
2.35.3

openSUSE Build Service is sponsored by