diff options
author | Anders Svensson <[email protected]> | 2017-09-03 13:32:35 +0200 |
---|---|---|
committer | Anders Svensson <[email protected]> | 2017-09-03 13:32:35 +0200 |
commit | d66d52396dadaa98b3180b6b0b947aaf94466f1c (patch) | |
tree | a0a6aab02cc365801e8e96591ca66f61c2ab92f5 /lib | |
parent | fdf359ff4063cf065f5355b9d13a2e76b6c9cd75 (diff) | |
parent | aa71291f765e901d7816e2399a3f3d55d41d06c9 (diff) | |
download | otp-d66d52396dadaa98b3180b6b0b947aaf94466f1c.tar.gz otp-d66d52396dadaa98b3180b6b0b947aaf94466f1c.tar.bz2 otp-d66d52396dadaa98b3180b6b0b947aaf94466f1c.zip |
Merge branch 'maint'
Diffstat (limited to 'lib')
-rw-r--r-- | lib/diameter/doc/src/diameter.xml | 8 | ||||
-rw-r--r-- | lib/diameter/doc/src/diameter_codec.xml | 18 | ||||
-rw-r--r-- | lib/diameter/doc/src/seealso.ent | 1 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter.erl | 2 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_config.erl | 9 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_gen.erl | 141 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_service.erl | 10 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_traffic.erl | 83 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_watchdog.erl | 4 | ||||
-rw-r--r-- | lib/diameter/src/transport/diameter_sctp.erl | 33 | ||||
-rw-r--r-- | lib/diameter/test/diameter_traffic_SUITE.erl | 139 |
11 files changed, 265 insertions, 183 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index b7be184058..0169afb619 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -799,7 +799,7 @@ be matched by corresponding &capability; configuration, of <tag> <marker id="decode_format"/> -<c>{decode_format, record | list | map | false}</c></tag> +<c>{decode_format, record | list | map | none}</c></tag> <item> <p> The format of decoded messages and grouped AVPs in the <c>msg</c> field @@ -808,10 +808,10 @@ records respectively. If <c>record</c> then a record whose definition is generated from the dictionary file in question. If <c>list</c> or <c>map</c> then a <c>[Name | Avps]</c> pair where -<c>Avps</c> is either a list of AVP name/values pairs or a map keyed on +<c>Avps</c> is a list of AVP name/values pairs or a map keyed on AVP names respectively. -If <c>false</c> then the representation is omitted and <c>msg</c> and -<c>value</c> fields are set to <c>false</c>. +If <c>none</c> then the atom-value message name, or <c>undefined</c> +for a Grouped AVP. See also &codec_message;.</p> <p> diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml index 0846334d23..5124b49484 100644 --- a/lib/diameter/doc/src/diameter_codec.xml +++ b/lib/diameter/doc/src/diameter_codec.xml @@ -4,7 +4,10 @@ '<seealso marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seealso>'> <!ENTITY types '<seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso>'> - <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY decode_format + '<seealso marker="diameter#decode_format">decode format</seealso>'> + +<!ENTITY % also SYSTEM "seealso.ent" > <!ENTITY % here SYSTEM "seehere.ent" > %also; %here; @@ -145,7 +148,8 @@ question.</p> <p> The decoded value of an AVP. Will be <c>undefined</c> on decode if the data bytes could -not be decoded or the AVP is unknown. +not be decoded, the AVP is unknown, or if the &decode_format; is +<c>none</c>. The type of a decoded value is as document in &types;.</p> </item> @@ -243,8 +247,7 @@ Equivalently, a message can also be encoded as a list whose head is the atom-valued message name (as specified in the relevant dictionary file) and whose tail is either a list of AVP name/values pairs or a map with values keyed on AVP names. -The format at decode is determined by &mod_service_opt; -<c>decode_format</c>. +The format at decode is determined by &mod_decode_format;. Any of the formats is accepted at encode.</p> <p> @@ -288,15 +291,16 @@ value other than <c>undefined</c>.</p> <item> <p> The incoming/outgoing message. -For an incoming message, a record if the message can be -decoded in a non-relay application, <c>undefined</c> otherwise. +For an incoming message, a term corresponding to the configured +&decode_format; if the message can be decoded in a non-relay +application, <c>undefined</c> otherwise. For an outgoing message, setting a <c>[&header; | &avp;]</c> list is equivalent to setting the <c>header</c> and <c>avps</c> fields to the corresponding values.</p> <warning> <p> -A record-valued <c>msg</c> field does <em>not</em> imply an absence of +A value in the <c>msg</c> field does <em>not</em> imply an absence of decode errors. The <c>errors</c> field should also be examined.</p> </warning> diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent index ef6af1a3d0..c5a53670d0 100644 --- a/lib/diameter/doc/src/seealso.ent +++ b/lib/diameter/doc/src/seealso.ent @@ -72,6 +72,7 @@ significant. <!ENTITY watchdog_timer '<seealso marker="#watchdog_timer">watchdog_timer</seealso>'> <!ENTITY mod_string_decode '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso> <seealso marker="diameter#string_decode">string_decode</seealso>'> +<!ENTITY mod_decode_format '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso> <seealso marker="diameter#decode_format">decode_format</seealso>'> <!-- diameter_app --> diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 3b41feac0d..69ef6f4ec0 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -340,7 +340,7 @@ call(SvcName, App, Message) -> :: record | list | map - | false + | none | record_from_map. -type strict_arities() diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 4721be1ca0..284f885884 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -588,8 +588,7 @@ opt(service, {K, false}) K == use_shared_peers; K == monitor; K == restrict_connections; - K == strict_arities; - K == decode_format -> + K == strict_arities -> true; opt(service, {K, true}) @@ -602,6 +601,7 @@ opt(service, {decode_format, T}) when T == record; T == list; T == map; + T == none; T == record_from_map -> true; @@ -665,9 +665,8 @@ opt(transport, {applications, As}) -> is_list(As); opt(transport, {capabilities, Os}) -> - is_list(Os) andalso case encode_CER(Os) of - ok -> true; - No -> {error, No} + is_list(Os) andalso try ok = encode_CER(Os), true + catch ?FAILURE(No) -> {error, No} end; opt(_, {K, Tmo}) diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl index 7a1a46ec52..0aea982a54 100644 --- a/lib/diameter/src/base/diameter_gen.erl +++ b/lib/diameter/src/base/diameter_gen.erl @@ -45,7 +45,7 @@ -define(THROW(T), throw({?MODULE, T})). -type parent_name() :: atom(). %% parent = Message or AVP --type parent_record() :: tuple(). %% +-type parent_record() :: tuple() | avp_values() | map(). -type avp_name() :: atom(). -type avp_record() :: tuple(). -type avp_values() :: [{avp_name(), term()}]. @@ -61,9 +61,7 @@ %% # encode_avps/3 %% --------------------------------------------------------------------------- --spec encode_avps(parent_name(), - parent_record() | avp_values() | map(), - map()) +-spec encode_avps(parent_name(), parent_record(), map()) -> iolist() | no_return(). @@ -232,13 +230,13 @@ enc(AvpName, Value, Opts, Mod) -> %% --------------------------------------------------------------------------- -spec decode_avps(parent_name(), binary(), map()) - -> {parent_record(), [avp()], Failed} + -> {parent_record() | parent_name(), [avp()], Failed} when Failed :: [{5000..5999, #diameter_avp{}}]. decode_avps(Name, Bin, #{module := Mod, decode_format := Fmt} = Opts) -> Strict = mget(strict_arities, Opts, decode), [AM, Avps, Failed | Rec] - = decode(Bin, Name, Mod, Fmt, Strict, Opts, #{}), + = decode(Bin, Name, Mod, Fmt, Strict, Opts, 0, #{}), %% AM counts the number of top-level AVPs, which missing/5 then %% uses when appending 5005 errors. {reformat(Name, Rec, Strict, Mod, Fmt), @@ -249,7 +247,7 @@ decode_avps(Name, Bin, #{module := Mod, decode_format := Fmt} = Opts) -> %% encountered. Failed-AVP should typically contain the first %% error encountered. -%% decode/7 +%% decode/8 decode(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>, Name, @@ -257,6 +255,7 @@ decode(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>, Fmt, Strict, Opts, + Idx, AM) -> decode(Rest, Code, @@ -270,21 +269,23 @@ decode(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>, Fmt, Strict, Opts, + Idx, AM); -decode(<<>>, Name, Mod, Fmt, Strict, _, AM) -> +decode(<<>>, Name, Mod, Fmt, Strict, _, _, AM) -> [AM, [], [] | newrec(Fmt, Mod, Name, Strict)]; -decode(Bin, Name, Mod, Fmt, Strict, _, AM) -> - Avp = #diameter_avp{data = Bin}, +decode(Bin, Name, Mod, Fmt, Strict, _, Idx, AM) -> + Avp = #diameter_avp{data = Bin, index = Idx}, [AM, [Avp], [{5014, Avp}] | newrec(Fmt, Mod, Name, Strict)]. -%% decode/13 +%% decode/14 -decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0, AM0) -> +decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0, + Idx, AM0) -> case Bin of <<Data:DataLen/binary, _:Pad/binary, T/binary>> -> - {NameT, AvpName, Arity, {Idx, AM}} + {NameT, Field, Arity, {I, AM}} = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0), Opts = setopts(NameT, Name, M, Opts0), @@ -300,11 +301,11 @@ decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0, AM0) - type = type(NameT), index = Idx}, - Dec = decode(Data, Name, NameT, Mod, Opts, Avp), %% decode - Acc = decode(T, Name, Mod, Fmt, Strict, Opts, AM), %% recurse - acc(Acc, Dec, Name, AvpName, Arity, Strict, Mod, Opts); + Dec = decode1(Data, Name, NameT, Mod, Fmt, Opts, Avp), + Acc = decode(T, Name, Mod, Fmt, Strict, Opts, Idx+1, AM),%% recurse + acc(Acc, Dec, I, Name, Field, Arity, Strict, Mod, Opts); _ -> - {NameT, _AvpName, _Arity, {Idx, AM}} + {NameT, _Field, _Arity, {_, AM}} = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0), Avp = #diameter_avp{code = Code, @@ -323,9 +324,14 @@ decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0, AM0) - incr(Name, Code, Vid, M, Mod, Strict, Opts, AM0) -> NameT = Mod:avp_name(Code, Vid), %% {AvpName, Type} | 'AVP' - AvpName = field(NameT), - Arity = avp_arity(Name, AvpName, Mod, Opts, M), - {NameT, AvpName, Arity, incr(AvpName, Arity, Strict, AM0)}. + Field = field(NameT), %% AvpName | 'AVP' + Arity = avp_arity(Name, Field, Mod, Opts, M), + if 0 == Arity, 'AVP' /= Field -> + A = pack_arity(Name, Field, Opts, Mod, M), + {NameT, 'AVP', A, incr('AVP', A, Strict, AM0)}; + true -> + {NameT, Field, Arity, incr(Field, Arity, Strict, AM0)} + end. %% Data is a truncated header if command_code = undefined, otherwise %% payload bytes. The former is padded to the length of a header if @@ -342,9 +348,8 @@ setopts({_, Type}, Name, M, Opts) -> %% incr/4 -incr(F, A, SA, AM) - when F == 'AVP'; - A == ?ANY; +incr(_, A, SA, AM) + when A == ?ANY; A == 0; SA /= decode -> {undefined, AM}; @@ -444,10 +449,10 @@ field({AvpName, _}) -> field(_) -> 'AVP'. -%% decode/6 +%% decode1/7 %% AVP not in dictionary. -decode(_Data, _Name, 'AVP', _Mod, _Opts, Avp) -> +decode1(_Data, _Name, 'AVP', _Mod, _Fmt, _Opts, Avp) -> Avp; %% 6733, 4.4: @@ -497,7 +502,7 @@ decode(_Data, _Name, 'AVP', _Mod, _Opts, Avp) -> %% defined the RFC's "unrecognized", which is slightly stronger than %% "not defined".) -decode(Data, Name, {AvpName, Type}, Mod, Opts, Avp) -> +decode1(Data, Name, {AvpName, Type}, Mod, Fmt, Opts, Avp) -> #{dictionary := AppMod, failed_avp := Failed} = Opts, @@ -511,26 +516,39 @@ decode(Data, Name, {AvpName, Type}, Mod, Opts, Avp) -> %% list of component AVPs. try avp_decode(Data, AvpName, Opts, DecMod, Mod) of - {Rec, As} when Type == 'Grouped' -> - A = Avp#diameter_avp{value = Rec}, - [A | As]; - V when Type /= 'Grouped' -> - Avp#diameter_avp{value = V} + V -> + set(Type, Fmt, Avp, V) catch throw: {?MODULE, T} -> - decode_error(Failed, T, Avp); + decode_error(Failed, Fmt, T, Avp); error: Reason -> decode_error(Failed, Reason, Name, Mod, Opts, Avp) end. -%% decode_error/3 +%% set/4 + +set('Grouped', none, Avp, V) -> + {_Rec, As} = V, + [Avp | As]; + +set('Grouped', _, Avp, V) -> + {Rec, As} = V, + [Avp#diameter_avp{value = Rec} | As]; + +set(_, _, Avp, V) -> + Avp#diameter_avp{value = V}. + +%% decode_error/4 %% %% Error when decoding a grouped AVP. -decode_error(true, {Rec, _, _}, Avp) -> +decode_error(true, none, _, Avp) -> + Avp; + +decode_error(true, _, {Rec, _, _}, Avp) -> Avp#diameter_avp{value = Rec}; -decode_error(false, {_, ComponentAvps, [{RC,A} | _]}, Avp) -> +decode_error(false, _, {_, ComponentAvps, [{RC,A} | _]}, Avp) -> {RC, [Avp | ComponentAvps], Avp#diameter_avp{data = [A]}}. %% decode_error/6 @@ -577,61 +595,55 @@ set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> set_failed(_, Opts) -> Opts. -%% acc/8 +%% acc/9 -acc([AM | Acc], As, Name, AvpName, Arity, Strict, Mod, Opts) -> - [AM | acc1(Acc, As, Name, AvpName, Arity, Strict, Mod, Opts)]. +acc([AM | Acc], As, I, Name, Field, Arity, Strict, Mod, Opts) -> + [AM | acc1(Acc, As, I, Name, Field, Arity, Strict, Mod, Opts)]. -%% acc1/8 +%% acc1/9 %% Faulty AVP, not grouped. -acc1(Acc, {_RC, Avp} = E, _, _, _, _, _, _) -> +acc1(Acc, {_RC, Avp} = E, _, _, _, _, _, _, _) -> [Avps, Failed | Rec] = Acc, [[Avp | Avps], [E | Failed] | Rec]; %% Faulty component in grouped AVP. -acc1(Acc, {RC, As, Avp}, _, _, _, _, _, _) -> +acc1(Acc, {RC, As, Avp}, _, _, _, _, _, _, _) -> [Avps, Failed | Rec] = Acc, [[As | Avps], [{RC, Avp} | Failed] | Rec]; %% Grouped AVP ... -acc1([Avps | Acc], [Avp|_] = As, Name, AvpName, Arity, Strict, Mod, Opts) -> - [[As|Avps] | acc2(Acc, Avp, Name, AvpName, Arity, Strict, Mod, Opts)]; +acc1([Avps | Acc], [Avp|_] = As, I, Name, Field, Arity, Strict, Mod, Opts) -> + [[As|Avps] | acc2(Acc, Avp, I, Name, Field, Arity, Strict, Mod, Opts)]; %% ... or not. -acc1([Avps | Acc], Avp, Name, AvpName, Arity, Strict, Mod, Opts) -> - [[Avp|Avps] | acc2(Acc, Avp, Name, AvpName, Arity, Strict, Mod, Opts)]. +acc1([Avps | Acc], Avp, I, Name, Field, Arity, Strict, Mod, Opts) -> + [[Avp|Avps] | acc2(Acc, Avp, I, Name, Field, Arity, Strict, Mod, Opts)]. -%% acc2/8 +%% acc2/9 %% No errors, but nowhere to pack. -acc2(Acc, Avp, _, 'AVP', 0, _, _, _) -> +acc2(Acc, Avp, _, _, 'AVP', 0, _, _, _) -> [Failed | Rec] = Acc, [[{rc(Avp), Avp} | Failed] | Rec]; -%% No AVP of this name: try to pack as 'AVP'. -acc2(Acc, Avp, Name, AvpName, 0, Strict, Mod, Opts) -> - M = Avp#diameter_avp.is_mandatory, - Arity = pack_arity(Name, AvpName, Opts, Mod, M), - acc2(Acc, Avp, Name, 'AVP', Arity, Strict, Mod, Opts); - %% Relaxed arities. -acc2(Acc, Avp, _, AvpName, Arity, Strict, Mod, _) +acc2(Acc, Avp, _, _, Field, Arity, Strict, Mod, _) when Strict /= decode -> - pack(Arity, AvpName, Avp, Mod, Acc); + pack(Arity, Field, Avp, Mod, Acc); %% No maximum arity. -acc2(Acc, Avp, _, AvpName, {_,'*'} = Arity, _, Mod, _) -> - pack(Arity, AvpName, Avp, Mod, Acc); +acc2(Acc, Avp, _, _, Field, {_,'*'} = Arity, _, Mod, _) -> + pack(Arity, Field, Avp, Mod, Acc); %% Or check. -acc2(Acc, Avp, _, AvpName, Arity, _, Mod, _) -> +acc2(Acc, Avp, I, _, Field, Arity, _, Mod, _) -> Mx = max_arity(Arity), - if Mx =< Avp#diameter_avp.index -> + if Mx =< I -> [Failed | Rec] = Acc, [[{5009, Avp} | Failed] | Rec]; true -> - pack(Arity, AvpName, Avp, Mod, Acc) + pack(Arity, Field, Avp, Mod, Acc) end. %% 3588/6733: @@ -723,8 +735,9 @@ pack(Arity, F, Avp, Mod, [Failed | Rec]) -> %% set/5 -set(_, _, _, _, false = No) -> - No; +set(_, _, _, _, None) + when is_atom(None) -> + None; set(1, F, Value, _, Map) when is_map(Map) -> @@ -818,8 +831,8 @@ empty(Name, #{module := Mod} = Opts) -> %% newrec/4 -newrec(false = No, _, _, _) -> - No; +newrec(none, _, Name, _) -> + Name; newrec(record, Mod, Name, T) when T /= decode -> diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 802e6e7243..3bd023a6f2 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -725,9 +725,13 @@ init_peers() -> %% TPid} service_opts(Opts) -> - T = {strict_arities, true}, - maps:merge(maps:from_list([{monitor, false} | def_opts() -- [T]]), - maps:from_list(Opts -- [T])). + remove([{strict_arities, true}], + maps:merge(maps:from_list([{monitor, false} | def_opts()]), + maps:from_list(Opts))). + +remove(List, Map) -> + maps:filter(fun(K,V) -> not lists:member({K,V}, List) end, + Map). def_opts() -> %% defaults on the service map [{share_peers, false}, diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index b89c4648d1..1a4bb4d0bf 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1032,15 +1032,15 @@ answer_message(RC, origin_realm = {OR,_}}, #diameter_packet{avps = Avps, errors = Es}) -> - {Code, _, Vid} = Dict0:avp_header('Session-Id'), ['answer-message', {'Origin-Host', OH}, {'Origin-Realm', OR}, - {'Result-Code', RC}] - ++ session_id(Code, Vid, Avps) - ++ failed_avp(RC, Es). + {'Result-Code', RC} + | session_id(Dict0, Avps) + ++ failed_avp(RC, Es) + ++ proxy_info(Dict0, Avps)]. -session_id(Code, Vid, Avps) - when is_list(Avps) -> +session_id(Dict0, Avps) -> + {Code, _, Vid} = Dict0:avp_header('Session-Id'), try #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps), [{'Session-Id', [Bin]}] @@ -1058,6 +1058,14 @@ failed_avp(RC, [_ | Es]) -> failed_avp(_, [] = No) -> No. +proxy_info(Dict0, Avps) -> + {Code, _, Vid} = Dict0:avp_header('Proxy-Info'), + [{'AVP', [A#diameter_avp{value = undefined} + || [#diameter_avp{code = C, vendor_id = I} = A | _] + <- Avps, + C == Code, + I == Vid]}]. + %% find_avp/3 %% Grouped ... @@ -1892,16 +1900,12 @@ str(T) -> %% get_avp/3 %% -%% Find an AVP in a message of one of three forms: -%% -%% - a message record (as generated from a .dia spec) or -%% - a list of an atom message name followed by 2-tuple, avp name/value pairs. -%% - a list of a #diameter_header{} followed by #diameter_avp{} records, -%% -%% In the first two forms a dictionary module is used at encode to -%% identify the type of the AVP and its arity in the message in -%% question. The third form allows messages to be sent as is, without -%% a dictionary, which is needed in the case of relay agents, for one. +%% Find an AVP in a message in one of the decoded formats, or as a +%% header/avps list. There are only four AVPs that are extracted here: +%% Result-Code and Experimental-Result in order when constructing +%% counter keys, and Destination-Host/Realm when selecting a next-hop +%% peer. Experimental-Result is the only of type Grouped, and is given +%% special treatment in order to return the value as a record. %% Messages will be header/avps list as a relay and the only AVP's we %% look for are in the common dictionary. This is required since the @@ -1910,12 +1914,12 @@ str(T) -> get_avp(?RELAY, Name, Msg) -> get_avp(?BASE, Name, Msg); -%% Message is a header/avps list. +%% Message as header/avps list. get_avp(Dict, Name, [#diameter_header{} | Avps]) -> try - {Code, _, VId} = Dict:avp_header(Name), - A = find_avp(Code, VId, Avps), - (avp_decode(Dict, Name, ungroup(A)))#diameter_avp{name = Name} + {Code, _, Vid} = Dict:avp_header(Name), + A = find_avp(Code, Vid, Avps), + avp_decode(Dict, Name, ungroup(A)) catch error: _ -> undefined @@ -1925,20 +1929,33 @@ get_avp(Dict, Name, [#diameter_header{} | Avps]) -> get_avp(_, Name, [_MsgName | Avps]) -> case find(Name, Avps) of {_, V} -> - #diameter_avp{name = Name, value = V}; + #diameter_avp{name = Name, value = value(Name, V)}; _ -> undefined end; -%% ... or record (but not necessarily). +%% ... or record. get_avp(Dict, Name, Rec) -> - try - #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)} + try Dict:'#get-'(Name, Rec) of + V -> + #diameter_avp{name = Name, value = value(Name, V)} catch error:_ -> undefined end. +value('Experimental-Result' = N, #{'Vendor-Id' := Vid, + 'Experimental-Result-Code' := RC}) -> + {N, Vid, RC}; +value('Experimental-Result' = N, [{'Experimental-Result-Code', RC}, + {'Vendor-Id', Vid}]) -> + {N, Vid, RC}; +value('Experimental-Result' = N, [{'Vendor-Id', Vid}, + {'Experimental-Result-Code', RC}]) -> + {N, Vid, RC}; +value(_, V) -> + V. + %% find/2 find(Key, Map) @@ -1968,14 +1985,25 @@ ungroup(Avp) -> %% avp_decode/3 +%% Ensure Experimental-Result is decoded as record, since this format +%% is used for counter keys. +avp_decode(Dict, 'Experimental-Result' = N, #diameter_avp{data = Bin} + = Avp) + when is_binary(Bin) -> + {V,_} = Dict:avp(decode, Bin, N, decode_opts(Dict)), + Avp#diameter_avp{name = N, value = V}; + avp_decode(Dict, Name, #diameter_avp{value = undefined, data = Bin} = Avp) when is_binary(Bin) -> V = Dict:avp(decode, Bin, Name, decode_opts(Dict)), - Avp#diameter_avp{value = V}; -avp_decode(_, _, #diameter_avp{} = Avp) -> - Avp. + Avp#diameter_avp{name = Name, value = V}; + +avp_decode(_, Name, #diameter_avp{} = Avp) -> + Avp#diameter_avp{name = Name}. + +%% cb/3 cb(#diameter_app{module = [_|_] = M}, F, A) -> eval(M, F, A). @@ -1992,4 +2020,5 @@ decode_opts(Dict) -> string_decode => false, strict_mbit => false, failed_avp => false, + module => Dict, dictionary => Dict}. diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index bb671e9860..c08e2da672 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -72,7 +72,7 @@ restrict := boolean(), suspect := non_neg_integer(), %% OKAY -> SUSPECT okay := non_neg_integer()}, %% REOPEN -> OKAY - codec :: #{decode_format := false, + codec :: #{decode_format := none, string_decode := false, strict_arities => diameter:strict_arities(), strict_mbit := boolean(), @@ -157,7 +157,7 @@ i({Ack, T, Pid, {Opts, string_decode, rfc, ordered_encode], - SvcOpts#{decode_format := false, + SvcOpts#{decode_format := none, string_decode := false, ordered_encode => false})}. diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 8de19e904e..4eb3379d59 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -102,6 +102,7 @@ streams :: {uint(), uint()} %% {InStream, OutStream} counts | undefined, os = 0 :: uint(), %% next output stream + rotate = 1 :: boolean() | 0 | 1, %% rotate os? packet = true :: boolean() %% legacy transport_data? | raw, message_cb = false :: false | diameter:eval(), @@ -692,11 +693,16 @@ send(#diameter_packet{transport_data = {outstream, SId}} = S) -> send(SId rem OS, Msg, S); -%% ... or not: rotate through all streams. -send(Msg, #transport{streams = {_, OS}, +%% ... or not: rotate when sending on multiple streams ... +send(Msg, #transport{rotate = true, + streams = {_, OS}, os = N} = S) -> - send(N, Msg, S#transport{os = (N + 1) rem OS}). + send(N, Msg, S#transport{os = (N + 1) rem OS}); + +%% ... or send on the only stream available. +send(Msg, S) -> + send(0, Msg, S). %% send/3 @@ -764,7 +770,7 @@ recv({[#sctp_sndrcvinfo{assoc_id = Id}], _Bin} %% Inbound Diameter message. recv({[#sctp_sndrcvinfo{}], Bin} = Msg, S) when is_binary(Bin) -> - message(recv, Msg, S); + message(recv, Msg, recv(S)); recv({_, #sctp_shutdown_event{}}, _) -> stop; @@ -784,6 +790,25 @@ recv({_, #sctp_paddr_change{}}, _) -> recv({_, #sctp_pdapi_event{}}, _) -> ok. +%% recv/1 +%% +%% Start sending unordered after the second reception, so that an +%% outgoing CER/CEA will arrive at the peer before another request. + +recv(#transport{rotate = B} = S) + when is_boolean(B) -> + S; + +recv(#transport{rotate = 0, streams = {_,N}, socket = Sock} = S) -> + ok = inet:setopts(Sock, [{sctp_default_send_param, + #sctp_sndrcvinfo{flags = [unordered]}}]), + S#transport{rotate = 1 < N}; + +recv(#transport{rotate = N} = S) -> + S#transport{rotate = N-1}. + +%% publish/4 + publish(T, Ref, Id, Sock) -> true = diameter_reg:add_new({?MODULE, T, {Ref, {Id, Sock}}}), putr(?INFO_KEY, {gen_sctp, Sock}). %% for info/1 diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 6f30055a0a..8f2549c8b6 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -49,6 +49,7 @@ send_protocol_error/1, send_experimental_result/1, send_arbitrary/1, + send_proxy_info/1, send_unknown/1, send_unknown_short/1, send_unknown_mandatory/1, @@ -114,6 +115,8 @@ %% diameter_{tcp,sctp} callbacks -export([message/3]). +-include_lib("kernel/include/inet_sctp.hrl"). + -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). -include("diameter_gen_base_accounting.hrl"). @@ -135,6 +138,7 @@ -define(A, list_to_atom). -define(L, atom_to_list). +-define(B, iolist_to_binary). %% Don't use is_record/2 since dictionary hrl's aren't included. %% (Since they define conflicting records with the same names.) @@ -154,7 +158,7 @@ -define(ENCODINGS, [list, record, map]). %% How to decode incoming messages. --define(DECODINGS, [record, false, map, list, record_from_map]). +-define(DECODINGS, [record, none, map, list, record_from_map]). %% Which dictionary to use in the clients. -define(RFCS, [rfc3588, rfc6733, rfc4005]). @@ -442,6 +446,7 @@ tc() -> send_protocol_error, send_experimental_result, send_arbitrary, + send_proxy_info, send_unknown, send_unknown_short, send_unknown_mandatory, @@ -507,6 +512,7 @@ start_services(Config) -> | ?SERVICE(SN, Grp)]), ok = diameter:start_service(CN, [{traffic_counters, bool()}, {sequence, ?CLIENT_MASK}, + {decode_format, map}, {strict_arities, decode} | ?SERVICE(CN, Grp)]). @@ -533,7 +539,7 @@ add_transports(Config) -> | server_apps()] ++ [{spawn_opt, {erlang, spawn, []}} || CS]), Cs = [?util:connect(CN, - [T, {sender, CS}], + [T, {sender, CS} | client_opts(T)], LRef, [{id, Id} | client_apps(R, [{'Origin-State-Id', origin(Id)}])]) @@ -543,6 +549,14 @@ add_transports(Config) -> Id <- [{D,E}]], ?util:write_priv(Config, "transport", [LRef | Cs]). +client_opts(tcp) -> + []; +client_opts(sctp) -> + [{sctp_initmsg, #sctp_initmsg{num_ostreams = N, + max_instreams = 5}} + || N <- [rand:uniform(8)], + N =< 6]. + server_apps() -> B = have_nas(), [{applications, [diameter_gen_base_rfc3588, @@ -684,6 +698,19 @@ send_arbitrary(Config) -> = call(Config, Req), "XXX" = string(V, Config). +%% Send Proxy-Info in an ASR that the peer answers with 3xxx, and +%% ensure that the AVP is returned. +send_proxy_info(Config) -> + H0 = ?B(?util:unique_string()), + S0 = ?B(?util:unique_string()), + Req = ['ASR', {'Proxy-Info', #{'Proxy-Host' => H0, + 'Proxy-State' => S0}}], + ['answer-message' | #{'Result-Code' := 3999, + 'Proxy-Info' := [#{'Proxy-Host' := H, + 'Proxy-State' := S}]}] + = call(Config, Req), + [H0, S0] = [?B(X) || X <- [H,S]]. + %% Send an unknown AVP (to some client) and check that it comes back. send_unknown(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, @@ -707,12 +734,12 @@ send_unknown_short(Config, M, RC) -> data = <<17>>}]}], ['ASA' | #{'Session-Id' := _, 'Result-Code' := RC, - 'Failed-AVP' := Avps}] + 'Failed-AVP' := [#{'AVP' := [Avp]}]}] = call(Config, Req), - [[#diameter_avp{code = 999, - is_mandatory = M, - data = <<17, _/binary>>}]] %% extra bits from padding - = failed_avps(Avps, Config). + #diameter_avp{code = 999, + is_mandatory = M, + data = <<17, _/binary>>} %% extra bits from padding + = Avp. %% Ditto but set the M flag. send_unknown_mandatory(Config) -> @@ -721,12 +748,12 @@ send_unknown_mandatory(Config) -> data = <<17>>}]}], ['ASA' | #{'Session-Id' := _, 'Result-Code' := ?AVP_UNSUPPORTED, - 'Failed-AVP' := Avps}] + 'Failed-AVP' := [#{'AVP' := [Avp]}]}] = call(Config, Req), - [[#diameter_avp{code = 999, - is_mandatory = true, - data = <<17>>}]] - = failed_avps(Avps, Config). + #diameter_avp{code = 999, + is_mandatory = true, + data = <<17>>} + = Avp. %% Ditto, and point the AVP length past the end of the message. Expect %% 5014 instead of 5001. @@ -741,13 +768,13 @@ send_unexpected_mandatory_decode(Config) -> data = <<12:32>>}]}], ['ASA' | #{'Session-Id' := _, 'Result-Code' := ?AVP_UNSUPPORTED, - 'Failed-AVP' := Avps}] + 'Failed-AVP' := [#{'AVP' := [Avp]}]}] = call(Config, Req), - [[#diameter_avp{code = 27, - is_mandatory = true, - value = 12, - data = <<12:32>>}]] - = failed_avps(Avps, Config). + #diameter_avp{code = 27, + is_mandatory = true, + value = 12, + data = <<12:32>>} + = Avp. %% Try to two Auth-Application-Id in ASR expect 5009. send_too_many(Config) -> @@ -755,11 +782,11 @@ send_too_many(Config) -> ['ASA' | #{'Session-Id' := _, 'Result-Code' := ?TOO_MANY, - 'Failed-AVP' := Avps}] + 'Failed-AVP' := [#{'AVP' := [Avp]}]}] = call(Config, Req), - [[#diameter_avp{name = 'Auth-Application-Id', - value = 44}]] - = failed_avps(Avps, Config). + #diameter_avp{name = 'Auth-Application-Id', + value = 44} + = Avp. %% Send an containing a faulty Grouped AVP (empty Proxy-Host in %% Proxy-Info) and expect that only the faulty AVP is sent in @@ -771,12 +798,11 @@ send_grouped_error(Config) -> {'Proxy-State', ""}]]}], ['ASA' | #{'Session-Id' := _, 'Result-Code' := ?INVALID_AVP_LENGTH, - 'Failed-AVP' := Avps}] + 'Failed-AVP' := [#{'AVP' := [Avp]}]}] = call(Config, Req), - [[#diameter_avp{name = 'Proxy-Info', value = V}]] - = failed_avps(Avps, Config), - {Empty, undefined, []} = proxy_info(V, Config), - <<0>> = iolist_to_binary(Empty). + #diameter_avp{name = 'Proxy-Info', value = #{'Proxy-Host' := H}} + = Avp, + <<0>> = ?B(H). %% Send an STR that the server ignores. send_noreply(Config) -> @@ -829,9 +855,8 @@ send_invalid_avp_length(Config) -> 'Result-Code' := ?INVALID_AVP_LENGTH, 'Origin-Host' := _, 'Origin-Realm' := _, - 'Failed-AVP' := Avps}] - = call(Config, Req), - [[_]] = failed_avps(Avps, Config). + 'Failed-AVP' := [#{'AVP' := [_]}]}] + = call(Config, Req). %% Send a request containing 5xxx errors that the server rejects with %% 3xxx. @@ -1042,29 +1067,6 @@ send_anything(Config) -> %% =========================================================================== -failed_avps(Avps, Config) -> - #group{client_dict = D} = proplists:get_value(group, Config), - [failed_avp(D, T) || T <- Avps]. - -failed_avp(nas4005, {'nas_Failed-AVP', As}) -> - As; -failed_avp(_, #'diameter_base_Failed-AVP'{'AVP' = As}) -> - As. - -proxy_info(Rec, Config) -> - #group{client_dict = D} = proplists:get_value(group, Config), - if D == nas4005 -> - {'nas_Proxy-Info', H, S, As} - = Rec, - {H,S,As}; - true -> - #'diameter_base_Proxy-Info'{'Proxy-Host' = H, - 'Proxy-State' = S, - 'AVP' = As} - = Rec, - {H,S,As} - end. - group(Config) -> #group{} = proplists:get_value(group, Config). @@ -1105,12 +1107,12 @@ origin(N) -> decode(record) -> 0; decode(list) -> 1; decode(map) -> 2; -decode(false) -> 3; +decode(none) -> 3; decode(record_from_map) -> 4; decode(0) -> record; decode(1) -> list; decode(2) -> map; -decode(3) -> false; +decode(3) -> none; decode(4) -> record_from_map. encode(record) -> 0; @@ -1157,16 +1159,17 @@ to_map(#diameter_packet{header = H, msg = Rec}, %% No record decode: do it ourselves. to_map(#diameter_packet{header = H, - msg = false, + msg = Name, bin = Bin}, - #group{server_decoding = false, + #group{server_decoding = none, strings = B}) -> Opts = #{decode_format => map, string_decode => B, strict_mbit => true, rfc => 6733}, - #diameter_packet{msg = [_MsgName | _Map] = Msg} + #diameter_packet{msg = [MsgName | _Map] = Msg} = diameter_codec:decode(dict(H), Opts, Bin), + {MsgName, _} = {Name, Msg}, %% assert Msg. dict(#diameter_header{application_id = Id, @@ -1534,24 +1537,23 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict = Dict0}) -> #diameter_packet{header = H, msg = Ans, errors = Es} = Pkt, ApplId = app(Req, Name, Dict0), #diameter_header{application_id = ApplId} = H, %% assert - Dict = dict(Ans, Dict0), - rec_to_map(answer(Ans, Es, Name), Dict). + answer(Ans, Es, Name). %% Missing Result-Code and inappropriate Experimental-Result-Code. -answer(Rec, Es, send_experimental_result) -> +answer(Ans, Es, send_experimental_result) -> [{5004, #diameter_avp{name = 'Experimental-Result'}}, {5005, #diameter_avp{name = 'Result-Code'}}] = Es, - Rec; + Ans; %% An inappropriate E-bit results in a decode error ... -answer(Rec, Es, send_bad_answer) -> +answer(Ans, Es, send_bad_answer) -> [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es, - Rec; + Ans; %% ... while other errors are reflected in Failed-AVP. -answer(Rec, [], _) -> - Rec. +answer(Ans, [], _) -> + Ans. app(_, send_unsupported_app, _) -> ?BAD_APP; @@ -1733,6 +1735,11 @@ request(['ACR' | #{'Accounting-Record-Number' := 4}], {'Origin-Realm', OR}], {reply, Ans}; +%% send_proxy_info +request(['ASR' | #{'Proxy-Info' := _}], + _) -> + {protocol_error, 3999}; + request(['ASR' | #{'Session-Id' := SId} = Avps], #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> |