Current section

Files

Jump to
mdns_lite src mdns_lite_inet_dns.erl
Raw

src/mdns_lite_inet_dns.erl

%%
%% %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(mdns_lite_inet_dns).
%% Dns record encode/decode
%%
%% RFC 1035: Domain Names - Implementation and Specification
%% RFC 2181: Clarifications to the DNS Specification
%% RFC 2671: Extension Mechanisms for DNS (EDNS0)
%% RFC 2782: A DNS RR for specifying the location of services (DNS SRV)
%% RFC 2915: The Naming Authority Pointer (NAPTR) DNS Resource Rec
%% RFC 6488: DNS Certification Authority Authorization (CAA) Resource Record
%% RFC 7553: The Uniform Resource Identifier (URI) DNS Resource Record
%% RFC 6762: Multicast DNS
-export([decode/1, encode/1]).
-import(lists, [reverse/1]).
-include("src/mdns_lite_inet_int.hrl").
-include("src/mdns_lite_inet_dns.hrl").
-export([record_type/1, rr/1, rr/2]).
-export([make_rr/0, make_rr/1, make_rr/2, make_rr/3]).
%% ADTs exports. The make_* functions are undocumented.
-export([msg/1, msg/2,
make_msg/0, make_msg/1, make_msg/2, make_msg/3]).
-export([header/1, header/2,
make_header/0, make_header/1, make_header/2, make_header/3]).
-export([dns_query/1, dns_query/2,
make_dns_query/0, make_dns_query/1,
make_dns_query/2, make_dns_query/3]).
-include("src/mdns_lite_inet_dns_record_adts.hrl").
%% Function merge of #dns_rr{} and #dns_rr_opt{}
%%
record_type(#dns_rr{}) -> rr;
record_type(#dns_rr_opt{}) -> rr;
record_type(Rec) ->
record_adts(Rec).
rr(#dns_rr{}=RR) -> dns_rr(RR);
rr(#dns_rr_opt{}=RR) -> dns_rr_opt(RR).
rr(#dns_rr{}=RR, L) -> dns_rr(RR, L);
rr(#dns_rr_opt{}=RR, L) -> dns_rr_opt(RR, L).
make_rr() -> make_dns_rr().
make_rr(L) when is_list(L) ->
case rr_type(L, any) of
opt -> make_dns_rr_opt(L);
_ -> make_dns_rr(L)
end.
make_rr(type, opt) -> make_dns_rr_opt();
make_rr(F, V) when is_atom(F) -> make_dns_rr(F, V);
make_rr(#dns_rr{}=RR, L) when is_list(L) ->
case rr_type(L, RR#dns_rr.type) of
opt ->
Ts = common_fields__rr__rr_opt(),
make_dns_rr_opt([Opt || {T,_}=Opt <- dns_rr(RR),
lists_member(T, Ts)] ++ L);
_ -> make_dns_rr(RR, L)
end;
make_rr(#dns_rr_opt{}=RR, L) when is_list(L) ->
case rr_type(L, RR#dns_rr_opt.type) of
opt ->
make_dns_rr_opt(RR, L);
_ ->
Ts = common_fields__rr__rr_opt(),
make_dns_rr([Opt || {T,_}=Opt <- dns_rr_opt(RR),
lists_member(T, Ts)] ++ L)
end.
make_rr(#dns_rr{}=RR, type, opt) -> make_rr(RR, [{type,opt}]);
make_rr(#dns_rr{}=RR, F, V) -> make_dns_rr(RR, F, V);
make_rr(#dns_rr_opt{}=RR, type, opt) -> RR;
make_rr(#dns_rr_opt{}=RR, type, T) -> make_rr(RR, [{type,T}]);
make_rr(#dns_rr_opt{}=RR, F, V) -> make_dns_rr_opt(RR, F, V).
-compile({inline, [rr_type/2]}).
rr_type([], T) -> T;
rr_type([{type,T}|Opts], _) -> rr_type(Opts, T);
rr_type([_|Opts], T) -> rr_type(Opts, T).
common_fields__rr__rr_opt() ->
[T || T <- record_info(fields, dns_rr_opt),
lists_member(T, record_info(fields, dns_rr))].
-compile({inline, [lists_member/2]}).
lists_member(_, []) -> false;
lists_member(H, [H|_]) -> true;
lists_member(H, [_|T]) -> lists_member(H, T).
%% must match a clause in inet_res:query_nss_e?dns
-define(DECODE_ERROR, formerr).
%%
%% Decode a dns buffer.
%%
%% Match macros that throw(?DECODE_ERROR) for no match
-define(
MATCH_ELSE_DECODE_ERROR(Match, Pattern, Result),
case begin Match end of
(Pattern) ->
begin Result end;
_ ->
throw(?DECODE_ERROR)
end).
-define(
MATCH_ELSE_DECODE_ERROR(Match, Pattern, Guard, Result),
case begin Match end of
(Pattern) when not not (Guard) ->
begin Result end;
_ ->
throw(?DECODE_ERROR)
end).
decode(Buffer) when is_binary(Buffer) ->
try do_decode(Buffer) of
DnsRec ->
{ok,DnsRec}
catch
Reason ->
{error,Reason}
end.
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(AnBuf,AnCount,Buffer),
{ArBuf,NsList,NsTC} = decode_rr_section(NsBuf,NsCount,Buffer),
{Rest,ArList,ArTC} = decode_rr_section(ArBuf,ArCount,Buffer),
?MATCH_ELSE_DECODE_ERROR(
Rest,
<<>>,
begin
HdrTC = decode_boolean(TC),
DnsHdr =
#dns_header{id=Id,
qr=decode_boolean(QR),
opcode=decode_opcode(Opcode),
aa=decode_boolean(AA),
tc=HdrTC,
rd=decode_boolean(RD),
ra=decode_boolean(RA),
pr=decode_boolean(PR),
rcode=Rcode},
?MATCH_ELSE_DECODE_ERROR(
%% Header marked as truncated, or no section
%% marked as truncated.
%% The converse; a section marked as truncated,
%% but not the header - is a parse error.
%%
HdrTC or (not (QdTC or AnTC or NsTC or ArTC)),
true,
begin
#dns_rec{header=DnsHdr,
qdlist=QdList,
anlist=AnList,
nslist=NsList,
arlist=ArList}
end)
end);
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(<<>>=Rest, N, _Buffer, Qs) ->
{Rest,reverse(Qs),N =/= 0};
decode_query_section(Rest, 0, _Buffer, Qs) ->
{Rest,reverse(Qs),false};
decode_query_section(Bin, N, Buffer, Qs) ->
?MATCH_ELSE_DECODE_ERROR(
decode_name(Bin, Buffer),
{<<T:16,C:16,Rest/binary>>,Name},
begin
{Class,UnicastResponse} = decode_class(C),
DnsQuery =
#dns_query{
domain = Name,
type = decode_type(T),
class = Class,
unicast_response = UnicastResponse},
decode_query_section(Rest, N-1, Buffer, [DnsQuery|Qs])
end).
decode_rr_section(Bin, N, Buffer) ->
decode_rr_section(Bin, N, Buffer, []).
decode_rr_section(<<>>=Rest, N, _Buffer, RRs) ->
{Rest,reverse(RRs),N =/= 0};
decode_rr_section(Rest, 0, _Buffer, RRs) ->
{Rest,reverse(RRs),false};
decode_rr_section(Bin, N, Buffer, RRs) ->
?MATCH_ELSE_DECODE_ERROR(
decode_name(Bin, Buffer),
{<<T:16/unsigned,C:16/unsigned,TTL:4/binary,
Len:16,D:Len/binary,Rest/binary>>,
Name},
begin
Type = decode_type(T),
RR =
case Type of
?S_OPT ->
<<ExtRcode,Version,Z:16>> = TTL,
#dns_rr_opt{
domain = Name,
type = Type,
udp_payload_size = C,
ext_rcode = ExtRcode,
version = Version,
z = Z,
data = D};
_ ->
{Class,CacheFlush} = decode_class(C),
Data = decode_data(D, Class, Type, Buffer),
<<TimeToLive:32/signed>> = TTL,
#dns_rr{
domain = Name,
type = Type,
class = Class,
ttl = max(0, TimeToLive),
data = Data,
func = CacheFlush}
end,
decode_rr_section(Rest, N-1, Buffer, [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),
B0 = encode_header(Q#dns_rec.header, QdCount, AnCount, NsCount, ArCount),
C0 = gb_trees:empty(),
{B1,C1} = encode_query_section(B0, C0, Q#dns_rec.qdlist),
{B2,C2} = encode_res_section(B1, C1, Q#dns_rec.anlist),
{B3,C3} = encode_res_section(B2, C2, Q#dns_rec.nslist),
{B,_} = encode_res_section(B3, C3, Q#dns_rec.arlist),
B.
%% RFC 1035: 4.1.1. Header section format
%%
encode_header(#dns_header{id=Id}=H, QdCount, AnCount, NsCount, ArCount) ->
QR = encode_boolean(H#dns_header.qr),
Opcode = encode_opcode(H#dns_header.opcode),
AA = encode_boolean(H#dns_header.aa),
TC = encode_boolean(H#dns_header.tc),
RD = encode_boolean(H#dns_header.rd),
RA = encode_boolean(H#dns_header.ra),
PR = encode_boolean(H#dns_header.pr),
Rcode = H#dns_header.rcode,
<<Id:16,
QR:1,Opcode:4,AA:1,TC:1,RD:1,
RA:1,PR:1,0:2,Rcode:4,
QdCount:16,AnCount:16,NsCount:16,ArCount:16>>.
%% 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]) ->
T = encode_type(Q#dns_query.type),
C = encode_class(Q#dns_query.class, 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).
%% RFC 1035: 4.1.3. Resource record format
%% RFC 2671: 4.3, 4.4, 4.6 OPT RR format
%%
encode_res_section(Bin, Comp, []) -> {Bin,Comp};
encode_res_section(
Bin, Comp,
[#dns_rr{
domain = DName,
type = Type,
class = Class,
func = CacheFlush,
ttl = TTL,
data = Data} | Rs]) ->
encode_res_section_rr(
Bin, Comp, Rs, DName, Type, Class, CacheFlush,
<<TTL:32/signed>>, Data);
encode_res_section(
Bin, Comp,
[#dns_rr_opt{
domain = DName,
udp_payload_size = UdpPayloadSize,
ext_rcode = ExtRCode,
version = Version,
z = Z,
data = Data} | Rs]) ->
encode_res_section_rr(
Bin, Comp, Rs, DName, ?S_OPT, UdpPayloadSize, false,
<<ExtRCode,Version,Z:16>>, Data).
encode_res_section_rr(
Bin0, Comp0, Rs, DName, Type, Class, CacheFlush, TTL, Data) ->
T = encode_type(Type),
C = encode_class(Class, CacheFlush),
{Bin,Comp1} = encode_name(Bin0, Comp0, byte_size(Bin0), DName),
Pos = byte_size(Bin)+2+2+byte_size(TTL)+2,
{DataBin,Comp} = encode_data(Comp1, Pos, Type, Class, Data),
DataSize = byte_size(DataBin),
encode_res_section(
<<Bin/binary,T:16,C:16,TTL/binary,DataSize:16,DataBin/binary>>,
Comp, Rs).
%%
%% Resource types
%%
decode_type(Type) ->
case Type of
?T_A -> ?S_A;
?T_NS -> ?S_NS;
?T_MD -> ?S_MD;
?T_MF -> ?S_MF;
?T_CNAME -> ?S_CNAME;
?T_SOA -> ?S_SOA;
?T_MB -> ?S_MB;
?T_MG -> ?S_MG;
?T_MR -> ?S_MR;
?T_NULL -> ?S_NULL;
?T_WKS -> ?S_WKS;
?T_PTR -> ?S_PTR;
?T_HINFO -> ?S_HINFO;
?T_MINFO -> ?S_MINFO;
?T_MX -> ?S_MX;
?T_TXT -> ?S_TXT;
?T_AAAA -> ?S_AAAA;
?T_SRV -> ?S_SRV;
?T_NAPTR -> ?S_NAPTR;
?T_OPT -> ?S_OPT;
?T_SPF -> ?S_SPF;
%% non standard
?T_UINFO -> ?S_UINFO;
?T_UID -> ?S_UID;
?T_GID -> ?S_GID;
?T_UNSPEC -> ?S_UNSPEC;
%% Query type values which do not appear in resource records
?T_AXFR -> ?S_AXFR;
?T_MAILB -> ?S_MAILB;
?T_MAILA -> ?S_MAILA;
?T_ANY -> ?S_ANY;
?T_URI -> ?S_URI;
?T_CAA -> ?S_CAA;
_ -> Type %% raw unknown type
end.
%%
%% Resource types
%%
encode_type(Type) ->
case Type of
?S_A -> ?T_A;
?S_NS -> ?T_NS;
?S_MD -> ?T_MD;
?S_MF -> ?T_MF;
?S_CNAME -> ?T_CNAME;
?S_SOA -> ?T_SOA;
?S_MB -> ?T_MB;
?S_MG -> ?T_MG;
?S_MR -> ?T_MR;
?S_NULL -> ?T_NULL;
?S_WKS -> ?T_WKS;
?S_PTR -> ?T_PTR;
?S_HINFO -> ?T_HINFO;
?S_MINFO -> ?T_MINFO;
?S_MX -> ?T_MX;
?S_TXT -> ?T_TXT;
?S_AAAA -> ?T_AAAA;
?S_SRV -> ?T_SRV;
?S_NAPTR -> ?T_NAPTR;
?S_OPT -> ?T_OPT;
?S_SPF -> ?T_SPF;
%% non standard
?S_UINFO -> ?T_UINFO;
?S_UID -> ?T_UID;
?S_GID -> ?T_GID;
?S_UNSPEC -> ?T_UNSPEC;
%% Query type values which do not appear in resource records
?S_AXFR -> ?T_AXFR;
?S_MAILB -> ?T_MAILB;
?S_MAILA -> ?T_MAILA;
?S_ANY -> ?T_ANY;
?S_URI -> ?T_URI;
?S_CAA -> ?T_CAA;
Type when is_integer(Type) -> Type %% raw unknown type
end.
%%
%% Resource classes
%%
decode_class(C0) ->
FlagBit = 16#8000,
C = C0 band (bnot FlagBit),
Class =
case C of
?C_IN -> in;
?C_CHAOS -> chaos;
?C_HS -> hs;
?C_ANY -> any;
_ -> C %% raw unknown class
end,
Flag = (C0 band FlagBit) =/= 0,
{Class,Flag}.
encode_class(Class, Flag) ->
C = encode_class(Class),
case Flag of
true -> FlagBit = 16#8000, C bor FlagBit;
false -> C
end.
%%
encode_class(Class) ->
case Class of
in -> ?C_IN;
chaos -> ?C_CHAOS;
hs -> ?C_HS;
any -> ?C_ANY;
Class when is_integer(Class) -> Class %% raw unknown class
end.
decode_opcode(Opcode) ->
case Opcode of
?QUERY -> 'query';
?IQUERY -> iquery;
?STATUS -> status;
_ when is_integer(Opcode) -> Opcode %% non-standard opcode
end.
encode_opcode(Opcode) ->
case Opcode of
'query' -> ?QUERY;
iquery -> ?IQUERY;
status -> ?STATUS;
_ when is_integer(Opcode) -> Opcode %% non-standard opcode
end.
encode_boolean(true) -> 1;
encode_boolean(false) -> 0;
encode_boolean(B) when is_integer(B) -> B.
decode_boolean(0) -> false;
decode_boolean(I) when is_integer(I) -> true.
%%
%% Data field -> term() content representation
%%
%% Class IN RRs
decode_data(Data, in, ?S_A, _) ->
?MATCH_ELSE_DECODE_ERROR(Data, <<A,B,C,D>>, {A,B,C,D});
decode_data(Data, in, ?S_AAAA, _) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16>>,
{A,B,C,D,E,F,G,H});
decode_data(Data, in, ?S_WKS, _) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<A,B,C,D,Proto,BitMap/binary>>,
{{A,B,C,D},Proto,BitMap});
%%
decode_data(Data, Class, Type, Buffer) ->
if
is_integer(Class) -> % Raw class
Data;
is_atom(Class) -> % Symbolic class, i.e: known
decode_data(Data, Type, Buffer)
end.
%%
%%
%% Standard RRs (any class)
decode_data(Data, ?S_SOA, Buffer) ->
{Data1,MName} = decode_name(Data, Buffer),
{Data2,RName} = decode_name(Data1, Buffer),
?MATCH_ELSE_DECODE_ERROR(
Data2,
<<Serial:32,Refresh:32/signed,Retry:32/signed,
Expiry:32/signed,Minimum:32>>,
{MName,RName,Serial,Refresh,Retry,Expiry,Minimum});
decode_data(Data, ?S_NS, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_MD, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_MF, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_CNAME, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_MB, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_MG, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_MR, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_PTR, Buffer) -> decode_domain(Data, Buffer);
decode_data(Data, ?S_NULL, _) -> Data;
decode_data(Data, ?S_HINFO, _) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<CpuLen,CPU:CpuLen/binary,OsLen,OS:OsLen/binary>>,
{binary_to_list(CPU),binary_to_list(OS)});
decode_data(Data, ?S_MINFO, Buffer) ->
{Data1,RM} = decode_name(Data, Buffer),
{Data2,EM} = decode_name(Data1, Buffer),
?MATCH_ELSE_DECODE_ERROR(Data2, <<>>, {RM,EM});
decode_data(Data, ?S_MX, Buffer) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Prio:16,Dom/binary>>,
{Prio,decode_domain(Dom, Buffer)});
decode_data(Data, ?S_SRV, Buffer) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Prio:16,Weight:16,Port:16,Dom/binary>>,
{Prio,Weight,Port,decode_domain(Dom, Buffer)});
decode_data(Data, ?S_NAPTR, Buffer) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Order:16,Preference:16,Data1/binary>>,
begin
{Data2,Flags} = decode_string(Data1),
{Data3,Services} = decode_string(Data2),
{Data4,Regexp} = decode_characters(Data3, utf8),
Replacement = decode_domain(Data4, Buffer),
{Order,Preference,
inet_db:tolower(Flags),inet_db:tolower(Services),
Regexp,Replacement}
end);
decode_data(Data, ?S_TXT, _) -> decode_txt(Data);
decode_data(Data, ?S_SPF, _) -> decode_txt(Data);
decode_data(Data, ?S_URI, _) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Prio:16,Weight:16,Data1/binary>>, 1 =< byte_size(Data1),
begin
Target = binary_to_list(Data1),
{Prio,Weight,Target}
end);
decode_data(Data, ?S_CAA, _) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Flags:8,Data1/binary>>,
begin
{Data2,Tag} = decode_string(Data1),
?MATCH_ELSE_DECODE_ERROR(
length(Tag),
L, 1 =< L andalso L =< 15,
begin
Value = binary_to_list(Data2),
{Flags,inet_db:tolower(Tag),Value}
end)
end);
%%
%% sofar unknown or non standard
decode_data(Data, Type, _) when is_integer(Type) ->
Data.
%% Array of strings
%%
decode_txt(<<>>) -> [];
decode_txt(Bin) ->
{Rest,String} = decode_string(Bin),
[String|decode_txt(Rest)].
decode_string(Data) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Len,Bin:Len/binary,Rest/binary>>,
{Rest,binary_to_list(Bin)}).
decode_characters(Data, Encoding) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
<<Len,Bin:Len/binary,Rest/binary>>,
{Rest,unicode:characters_to_list(Bin, Encoding)}).
%% One domain name only, there must be nothing after
%%
decode_domain(Bin, Buffer) ->
?MATCH_ELSE_DECODE_ERROR(decode_name(Bin, Buffer), {<<>>,Name}, Name).
%% Domain name -> {RestBin,Name}
%%
decode_name(Bin, Buffer) ->
decode_name(Bin, Buffer, [], Bin, 0).
%% Tail advances with Rest until the first indirection is followed
%% then it stays put at that Rest.
decode_name(_, Buffer, _Labels, _Tail, Cnt) when Cnt > byte_size(Buffer) ->
throw(?DECODE_ERROR); %% Insanity bailout - this must be a decode loop
decode_name(<<0,Rest/binary>>, _Buffer, Labels, Tail, Cnt) ->
%% Root domain, we have all labels for the domain name
{if Cnt =/= 0 -> Tail; true -> Rest end,
decode_name_labels(Labels)};
decode_name(<<0:2,Len:6,Label:Len/binary,Rest/binary>>,
Buffer, Labels, Tail, Cnt) ->
%% One plain label here
decode_name(Rest, Buffer, [Label|Labels],
if Cnt =/= 0 -> Tail; true -> Rest end,
Cnt);
decode_name(<<3:2,Ptr:14,Rest/binary>>, Buffer, Labels, Tail, Cnt) ->
%% Indirection - reposition in buffer and recurse
?MATCH_ELSE_DECODE_ERROR(
Buffer,
<<_:Ptr/binary,Bin/binary>>,
decode_name(
Bin, Buffer, Labels,
if Cnt =/= 0 -> Tail; true -> Rest end,
Cnt+2)); % size of indirection pointer
decode_name(_, _, _, _, _) -> throw(?DECODE_ERROR).
%% Reverse list of labels (binaries) -> domain name (string)
decode_name_labels([]) -> ".";
decode_name_labels(Labels) ->
decode_name_labels(Labels, "").
decode_name_labels([Label], Name) ->
decode_name_label(Label, Name);
decode_name_labels([Label|Labels], Name) ->
decode_name_labels(Labels, "."++decode_name_label(Label, Name)).
decode_name_label(Label, Name) ->
?MATCH_ELSE_DECODE_ERROR(
Label,
_, 1 =< byte_size(Label),
%% Empty label is only allowed for the root domain,
%% and that is handled above.
decode_name_label(Label, Name, byte_size(Label))).
%% Decode $. and $\\ to become $\\ escaped characters
%% in the string representation.
-compile({inline, [decode_name_label/3]}).
decode_name_label(_, Name, 0) -> Name;
decode_name_label(Label, Name, N) ->
M = N-1,
case Label of
<<_:M/binary,($\\),_/binary>> ->
decode_name_label(Label, "\\\\"++Name, M);
<<_:M/binary,($.),_/binary>> ->
decode_name_label(Label, "\\."++Name, M);
<<_:M/binary,C,_/binary>> ->
decode_name_label(Label, [C|Name], M);
_ ->
%% This should not happen but makes surrounding
%% programming errors easier to locate.
erlang:error(badarg, [Label,Name,N])
end.
%%
%% Data field -> {binary(),NewCompressionTable}
%%
%% Class IN RRs
encode_data(Comp, _, ?S_A, in, Addr) ->
{A,B,C,D} = Addr,
{<<A,B,C,D>>,Comp};
encode_data(Comp, _, ?S_AAAA, in, Addr) ->
{A,B,C,D,E,F,G,H} = Addr,
{<<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16>>,Comp};
encode_data(Comp, _, ?S_WKS, in, Data) ->
{{A,B,C,D},Proto,BitMap} = Data,
BitMapBin = iolist_to_binary(BitMap),
{<<A,B,C,D,Proto,BitMapBin/binary>>,Comp};
%% OPT pseudo-RR (of no class) - should not take this way;
%% this must be a #dns_rr{type = ?S_OPT} instead of a #dns_rr_opt{},
%% so good luck getting in particular Class and TTL right...
encode_data(Comp, _, ?S_OPT, _UdpPayloadSize, Data) ->
encode_data(Comp, Data);
%%
encode_data(Comp, Pos, Type, Class, Data) ->
if
is_integer(Class) ->
encode_data(Comp, Data);
is_atom(Class) -> % Known Class
encode_data(Comp, Pos, Type, Data)
end.
%%
%%
%% Standard RRs (any class)
encode_data(Comp, Pos, ?S_SOA, Data) ->
{MName,RName,Serial,Refresh,Retry,Expiry,Minimum} = Data,
{B1,Comp1} = encode_name(Comp, Pos, MName),
{B,Comp2} = encode_name(B1, Comp1, Pos+byte_size(B1), RName),
{<<B/binary,Serial:32,Refresh:32/signed,Retry:32/signed,
Expiry:32/signed,Minimum:32>>,
Comp2};
encode_data(Comp, Pos, ?S_NS, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_MD, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_MF, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_CNAME, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_MB, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_MG, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_MR, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, Pos, ?S_PTR, Domain) -> encode_name(Comp, Pos, Domain);
encode_data(Comp, _, ?S_NULL, Data) -> encode_data(Comp, Data);
encode_data(Comp, _, ?S_HINFO, Data) ->
{CPU,OS} = Data,
Bin = encode_string(iolist_to_binary(CPU)),
{encode_string(Bin, iolist_to_binary(OS)),Comp};
encode_data(Comp, Pos, ?S_MINFO, Data) ->
{RM,EM} = Data,
{Bin,Comp1} = encode_name(Comp, Pos, RM),
encode_name(Bin, Comp1, Pos+byte_size(Bin), EM);
encode_data(Comp, Pos, ?S_MX, Data) ->
{Pref,Exch} = Data,
encode_name(<<Pref:16>>, Comp, Pos+2, Exch);
encode_data(Comp, Pos, ?S_SRV, Data) ->
{Prio,Weight,Port,Target} = Data,
encode_name(<<Prio:16,Weight:16,Port:16>>, Comp, Pos+2+2+2, Target);
encode_data(Comp, Pos, ?S_NAPTR, Data) ->
{Order,Preference,Flags,Services,Regexp,Replacement} = Data,
B0 = <<Order:16,Preference:16>>,
B1 = encode_string(B0, iolist_to_binary(Flags)),
B2 = encode_string(B1, iolist_to_binary(Services)),
B3 = encode_string(B2, unicode:characters_to_binary(Regexp,
unicode, utf8)),
%% Bypass name compression (RFC 2915: section 2)
{B,_} = encode_name(B3, gb_trees:empty(), Pos+byte_size(B3), Replacement),
{B,Comp};
encode_data(Comp, _, ?S_TXT, Data) -> {encode_txt(Data),Comp};
encode_data(Comp, _, ?S_SPF, Data) -> {encode_txt(Data),Comp};
encode_data(Comp, _, ?S_URI, Data) ->
{Prio,Weight,Target} = Data,
{<<Prio:16,Weight:16,(iolist_to_binary(Target))/binary>>,Comp};
encode_data(Comp, _, ?S_CAA, Data)->
case Data of
{Flags,Tag,Value} ->
B0 = <<Flags:8>>,
B1 = encode_string(B0, iolist_to_binary(Tag)),
B2 = iolist_to_binary(Value),
{<<B1/binary,B2/binary>>,Comp};
_ ->
{encode_txt(Data),Comp}
end;
%%
%% sofar unknown or non standard
encode_data(Comp, _Pos, Type, Data) when is_integer(Type) ->
encode_data(Comp, Data).
%%
%% Passthrough
-compile({inline, [encode_data/2]}).
encode_data(Comp, Data) ->
{iolist_to_binary(Data),Comp}.
%% Array of strings
%%
encode_txt(Strings) ->
encode_txt(<<>>, Strings).
%%
encode_txt(Bin, []) -> Bin;
encode_txt(Bin, [S|Ss]) ->
encode_txt(encode_string(Bin, iolist_to_binary(S)), Ss).
%% Singular string
%%
encode_string(StringBin) ->
encode_string(<<>>, StringBin).
%%
encode_string(Bin, StringBin) ->
Size = byte_size(StringBin),
if Size =< 255 ->
<<Bin/binary,Size,StringBin/binary>>
end.
%% Domain name
%%
encode_name(Comp, Pos, Name) ->
encode_name(<<>>, Comp, Pos, Name).
%%
%% Bin = target binary
%% Comp = compression lookup table; label list -> buffer position
%% Pos = position in DNS message
%% Name = domain name to encode
%%
%% The name compression does not make the case conversions
%% it could. This means case will be preserved at the cost
%% of missed compression opportunities. But if the encoded
%% message use the same case for different instances of
%% the same domain name there is no problem, and if not it is
%% only compression that suffers. Furthermore encode+decode
%% this way becomes an identity operation for any decoded
%% DNS message which is nice for testing encode.
%%
encode_name(Bin0, Comp0, Pos, Name) ->
case encode_labels(Bin0, Comp0, Pos, name2labels(Name)) of
{Bin,_}=Result when byte_size(Bin) - byte_size(Bin0) =< 255 -> Result;
_ ->
%% Fail on too long name
erlang:error(badarg, [Bin0,Comp0,Pos,Name])
end.
name2labels("") -> [];
name2labels(".") -> [];
name2labels(Cs) -> name2labels(<<>>, Cs).
%%
name2labels(Label, "") -> [Label];
name2labels(Label, ".") -> [Label];
name2labels(Label, "."++Cs) -> [Label|name2labels(<<>>, Cs)];
name2labels(Label, "\\"++[C|Cs]) -> name2labels(Label, Cs, C);
name2labels(Label, [C|Cs]) -> name2labels(Label, Cs, C).
%%
-compile({inline, [name2labels/3]}).
name2labels(Label, Cs, C) when is_integer(C), 0 =< C, C =< 255 ->
name2labels(<<Label/binary,C>>, Cs).
%% Fail on empty or too long labels.
encode_labels(Bin, Comp, _Pos, []) ->
{<<Bin/binary,0>>,Comp};
encode_labels(Bin, Comp0, Pos, [L|Ls]=Labels)
when 1 =< byte_size(L), byte_size(L) =< 63 ->
case gb_trees:lookup(Labels, Comp0) of
none ->
Comp = if Pos < (1 bsl 14) ->
%% Just in case - compression
%% pointers cannot reach further
gb_trees:insert(Labels, Pos, Comp0);
true -> Comp0
end,
Size = byte_size(L),
encode_labels(<<Bin/binary,Size,L/binary>>,
Comp, Pos+1+Size, Ls);
{value,Ptr} ->
%% Name compression - point to already encoded name
{<<Bin/binary,3:2,Ptr:14>>,Comp0}
end.