aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter')
-rw-r--r--lib/diameter/doc/src/diameter.xml24
-rw-r--r--lib/diameter/doc/src/diameter_dict.xml2
-rw-r--r--lib/diameter/doc/src/notes.xml224
-rw-r--r--lib/diameter/include/diameter_gen.hrl4
-rw-r--r--lib/diameter/src/base/diameter.erl5
-rw-r--r--lib/diameter/src/base/diameter_codec.erl10
-rw-r--r--lib/diameter/src/base/diameter_config.erl14
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl18
-rw-r--r--lib/diameter/src/base/diameter_service.erl6
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl191
-rw-r--r--lib/diameter/src/base/diameter_types.erl29
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl4
-rw-r--r--lib/diameter/src/compiler/diameter_forms.hrl14
-rw-r--r--lib/diameter/src/diameter.appup.src78
-rw-r--r--lib/diameter/test/diameter_3xxx_SUITE.erl200
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl51
-rw-r--r--lib/diameter/test/diameter_capx_SUITE.erl12
-rw-r--r--lib/diameter/test/diameter_codec_test.erl10
-rw-r--r--lib/diameter/test/diameter_config_SUITE.erl9
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl122
-rw-r--r--lib/diameter/vsn.mk2
21 files changed, 806 insertions, 223 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 37e67d8630..ea175a58b8 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -783,6 +783,27 @@ be matched by corresponding &capability; configuration, of
</item>
+<marker id="incoming_maxlen"/>
+<tag><c>{incoming_maxlen, 0..16777215}</c></tag>
+<item>
+<p>
+Bound on the expected size of incoming Diameter messages.
+Messages larger than the specified number of bytes are discarded.</p>
+
+<p>
+Defaults to <c>16777215</c>, the maximum value of the 24-bit Message
+Length field in a Diameter Header.</p>
+
+<warning>
+<p>
+This option should be set to as low a value as is sufficient for the
+Diameter applications and peers in question, since decoding incoming
+messages from a malicious peer can otherwise generate significant
+load.</p>
+</warning>
+
+</item>
+
<tag><c>{restrict_connections, false
| node
| nodes
@@ -1799,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except
that the client connections are grouped under an <c>accept</c> tuple.</p>
<p>
-Whether or not the &transport_opt; <c>pool_size</c> affects the format
+Whether or not the &transport_opt; <c>pool_size</c> has been
+configured affects the format
of the listing in the case of a connecting transport, since a value
greater than 1 implies multiple transport processes for the same
<c>&transport_ref;</c>, as in the listening case.
diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml
index 9db9bcffde..5cf1b174a0 100644
--- a/lib/diameter/doc/src/diameter_dict.xml
+++ b/lib/diameter/doc/src/diameter_dict.xml
@@ -529,7 +529,7 @@ answer record and passed to a &app_handle_request;
callback upon reception of an incoming request.</p>
<p>
-In cases in which there is a choice between list() and binary() types
+In cases in which there is a choice between string() and binary() types
for OctetString() and derived types, the representation is determined
by the value of &mod_string_decode;.</p>
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index e6ac332c10..6931788c83 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -42,6 +42,230 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 1.9.1</title>
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ Don't leave extra bit in decoded AVP data.</p>
+ <p>
+ OTP-12074 in OTP 17.3 missed one case: a length error on
+ a trailing AVP unknown to the dictionary in question.</p>
+ <p>
+ Own Id: OTP-12642</p>
+ </item>
+ <item>
+ <p>
+ Don't confuse Result-Code and Experimental-Result</p>
+ <p>
+ The errors field of a decoded diameter_packet record was
+ populated with a Result-Code AVP when an
+ Experimental-Result containing a 3xxx Result-Code was
+ received in an answer not setting the E-bit. The correct
+ AVP is now extracted from the incoming message.</p>
+ <p>
+ Own Id: OTP-12654 Aux Id: seq12851 </p>
+ </item>
+ <item>
+ <p>
+ Don't count on unknown Application Id.</p>
+ <p>
+ OTP-11721 in OTP 17.1 missed the case of an Application
+ Id not agreeing with that of the dictionary in question,
+ causing counters to be accumulated on keys containing the
+ unknown id.</p>
+ <p>
+ Own Id: OTP-12701</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>diameter 1.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Don't discard outgoing answers unnecessarily.</p>
+ <p>
+ Answers missing a Result-Code AVP or setting an E-bit
+ inappropriately were discarded even if encode was
+ successful.</p>
+ <p>
+ Own Id: OTP-11492</p>
+ </item>
+ <item>
+ <p>
+ Increase supervision timeouts.</p>
+ <p>
+ At diameter application shutdown, DPR could be omitted on
+ open peer connections because of short supervision
+ timeouts.</p>
+ <p>
+ Own Id: OTP-12412</p>
+ </item>
+ <item>
+ <p>
+ Fix retransmission of messages sent as header/avps list.</p>
+ <p>
+ Extracting End-to-End and Hop-by-Hop Identifiers resulted
+ in a function clause error, resulting in a handle_error
+ callback.</p>
+ <p>
+ Own Id: OTP-12415</p>
+ </item>
+ <item>
+ <p>
+ Fix diameter_avp decode of Grouped AVPs having decode
+ errors.</p>
+ <p>
+ Components of such an AVP were not extracted, causing it
+ to be represented by a single diameter_avp record instead
+ of the intended list.</p>
+ <p>
+ Dictionary files must be recompiled for the fix to have
+ effect.</p>
+ <p>
+ Own Id: OTP-12475</p>
+ </item>
+ <item>
+ <p>
+ Fix ordering of AVPs in relayed messages.</p>
+ <p>
+ The order was reversed relative to the received order,
+ with a Route-Record AVP prepended.</p>
+ <p>
+ Thanks to Andrzej TrawiƄski.</p>
+ <p>
+ Own Id: OTP-12551</p>
+ </item>
+ <item>
+ <p>
+ Fix issues with DiameterURI encode/decode.</p>
+ <p>
+ RFC 6773 changed the default port and transport, but the
+ RFC 3588 defaults were used even if the RFC 6733 common
+ dictionary was in use. The RFC 3588 defaults are now only
+ used when the common dictionary is
+ diameter_gen_base_rfc3588.</p>
+ <p>
+ Both RFC 3588 and 6733 disallow
+ transport=udp;protocol=diameter. Encode of the
+ combination now fails.</p>
+ <p>
+ Decode of ports numbers outside the range 0-65535 and
+ fully qualified domain names longer than 255 octets now
+ fails.</p>
+ <p>
+ Note that RFC 3588 is obsolete, and that there is a
+ diameter_gen_base_rfc6733. The change in defaults is a
+ potential interoperability problem when moving to RFC
+ 6733 with peers that do not send all URI components. The
+ fact that 6733 allows 5xxx result codes in answer
+ messages setting the E-bit, which RFC 3588 doesn't, is
+ another.</p>
+ <p>
+ Own Id: OTP-12589</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add service_opt() string_decode.</p>
+ <p>
+ To disable the decode of potentially large binaries to
+ string. This prevents large strings from being copied
+ when incoming Diameter messages are passed between
+ processes, a vulnerability that can lead to memory being
+ exhausted given sufficiently malicious peers.</p>
+ <p>
+ The value is a boolean(), true being the default for
+ backwards compatibility. Setting false causes both
+ diameter_caps records and decoded messages to contain
+ binary() in relevant places that previously had string():
+ diameter_app(3) callbacks need to be prepared for the
+ change.</p>
+ <p>
+ The Diameter types affected are OctetString and the
+ derived types UTF8String, DiameterIdentity, DiameterURI,
+ IPFilterRule, and QoSFilterRule. Time and Address are
+ unaffected.</p>
+ <p>
+ Own Id: OTP-11952</p>
+ </item>
+ <item>
+ <p>
+ Add transport_opt() pool_size.</p>
+ <p>
+ To allow for pools of accepting transport processes,
+ which can better service multiple simultaneous peer
+ connections. The option can also be used with connecting
+ transports, to establish multiple connections to the same
+ peer without having to configure multiple transports.</p>
+ <p>
+ Own Id: OTP-12428</p>
+ </item>
+ <item>
+ <p>
+ Allow DPR to be sent with diameter:call/4.</p>
+ <p>
+ It has been possible to send, but the answer was regarded
+ as unsolicited and discarded. DPA now causes the
+ transport process in question to be terminated, as for
+ DPR that diameter itself sends.</p>
+ <p>
+ Own Id: OTP-12542</p>
+ </item>
+ <item>
+ <p>
+ Discard requests after DPR.</p>
+ <p>
+ RFC 6733 is imprecise, but the tone is that messages
+ received after DPR are an exception to be dealt with only
+ because of the possibility of unordered delivery over
+ SCTP. As a consequence, and because a request following
+ DPR is unlikely to be answered due to the impending loss
+ of the peer connection, discard outgoing requests
+ following an outgoing or incoming DPR. Incoming requests
+ are also discarded, with the exception of DPR itself.
+ Answers are sent and received as usual.</p>
+ <p>
+ Own Id: OTP-12543</p>
+ </item>
+ <item>
+ <p>
+ Add transport_opt() dpr_timeout.</p>
+ <p>
+ To cause a peer connection to be closed following an
+ outgoing DPA when the peer fails to do so. It is the
+ recipient of DPA that should close the connection
+ according to RFC 6733.</p>
+ <p>
+ Own Id: OTP-12609</p>
+ </item>
+ <item>
+ <p>
+ Add service_opt() incoming_maxlen.</p>
+ <p>
+ To bound the expected size of incoming Diameter messages.
+ Messages larger than the specified number of bytes are
+ discarded, to prevent a malicious peer from generating
+ excessive load.</p>
+ <p>
+ Own Id: OTP-12628</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 1.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index 8272904856..e8ffe7f92c 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -395,7 +395,7 @@ d(false, Reason, Name, Avp, {Avps, Acc}) ->
diameter_lib:log(decode_error,
?MODULE,
?LINE,
- {Reason, Name, Avp#diameter_avp.name, Stack}),
+ {Name, Avp#diameter_avp.name, Stack}),
{Rec, Failed} = Acc,
{[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}.
@@ -445,7 +445,7 @@ reset(_, _) ->
%% undecoded. Note that the type field is 'undefined' in this case.
decode_AVP(Name, Avp, {Avps, Acc}) ->
- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}.
+ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}.
%% rc/1
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index 67dfc7bdbf..010f977b97 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -45,6 +45,7 @@
-export_type([evaluable/0,
restriction/0,
+ message_length/0,
remotes/0,
sequence/0,
app_alias/0,
@@ -298,6 +299,9 @@ call(SvcName, App, Message) ->
| [node()]
| evaluable().
+-type message_length()
+ :: 0..16#FFFFFF.
+
%% Options passed to start_service/2
-type service_opt()
@@ -307,6 +311,7 @@ call(SvcName, App, Message) ->
| {sequence, sequence() | evaluable()}
| {share_peers, remotes()}
| {string_decode, boolean()}
+ | {incoming_maxlen, message_length()}
| {use_shared_peers, remotes()}
| {spawn_opt, list()}.
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 15a4c5e86f..bf2fe8e7ca 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -640,8 +640,12 @@ split_data(Bin, Len) ->
%% payload if this is a request. Do this (in cases that we
%% know the type) by inducing a decode failure and letting
%% the dictionary's decode (in diameter_gen) deal with it.
- %% Here we don't know type. If the type isn't known, then
- %% the decode just strips the extra bit.
+ %%
+ %% Note that the extra bit can only occur in the trailing
+ %% AVP of a message or Grouped AVP, since a faulty AVP
+ %% Length is otherwise indistinguishable from a correct
+ %% one here, since we don't know the types of the AVPs
+ %% being extracted.
{<<0:1, Bin/binary>>, <<>>}
end.
@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B})
Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
<<H/binary, Len:24, T/binary>>;
-%% ... from a dictionary compiled against old code in diameter_gen ...
%% ... when ignoring errors in Failed-AVP ...
+%% ... during a relay encode ...
pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
pack_avp(A#diameter_avp{data = B});
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 0d0304bf33..8ac3b9d6ca 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -159,7 +159,8 @@ stop_service(SvcName) ->
%% # add_transport/2
%% --------------------------------------------------------------------------
--spec add_transport(diameter:service_name(), {connect|listen, [diameter:transport_opt()]})
+-spec add_transport(diameter:service_name(),
+ {connect|listen, [diameter:transport_opt()]})
-> {ok, diameter:transport_ref()}
| {error, term()}.
@@ -645,6 +646,7 @@ make_config(SvcName, Opts) ->
{false, monitor},
{?NOMASK, sequence},
{nodes, restrict_connections},
+ {16#FFFFFF, incoming_maxlen},
{true, string_decode},
{[], spawn_opt}]),
@@ -670,12 +672,20 @@ make_opts(Opts, Defs) ->
[{K, opt(K,V)} || {K,V} <- Known].
+opt(incoming_maxlen, N)
+ when 0 =< N, N < 1 bsl 24 ->
+ N;
+
opt(spawn_opt, L)
when is_list(L) ->
L;
opt(K, false = B)
- when K /= sequence ->
+ when K == share_peers;
+ K == use_shared_peers;
+ K == monitor;
+ K == restrict_connections;
+ K == string_decode ->
B;
opt(K, true = B)
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index aac2685514..2255d0a76b 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -125,7 +125,8 @@
%% outgoing DPR; boolean says whether or not
%% the request was sent explicitly with
%% diameter:call/4.
- length_errors :: exit | handle | discard}).
+ length_errors :: exit | handle | discard,
+ incoming_maxlen :: integer() | infinity}).
%% There are non-3588 states possible as a consequence of 5.6.1 of the
%% standard and the corresponding problem for incoming CEA's: we don't
@@ -203,6 +204,7 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
diameter_stats:reg(Ref),
diameter_codec:setopts([{common_dictionary, Dict0} | SvcOpts]),
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
+ Maxlen = proplists:get_value(incoming_maxlen, SvcOpts, 16#FFFFFF),
{[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]),
putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}),
putr(?DPR_KEY, [F || {_, F} <- Ds]),
@@ -223,7 +225,8 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
dictionary = Dict0,
mode = M,
service = svc(Svc, Addrs),
- length_errors = OnLengthErr}.
+ length_errors = OnLengthErr,
+ incoming_maxlen = Maxlen}.
%% The transport returns its local ip addresses so that different
%% transports on the same service can use different local addresses.
%% The local addresses are put into Host-IP-Address avps here when
@@ -326,11 +329,14 @@ handle_info(T, #state{} = State) ->
{?MODULE, Tag, Reason} ->
?LOG(stop, Tag),
{stop, {shutdown, Reason}, State}
- end.
+ end;
%% The form of the throw caught here is historical. It's
%% significant that it's not a 2-tuple, as in ?FAILURE(Reason),
%% since these are caught elsewhere.
+handle_info(T, S) -> %% started in old code
+ handle_info(T, #state{} = erlang:append_element(S, infinity)).
+
%% Note that there's no guarantee that the service and transport
%% capabilities are good enough to build a CER/CEA that can be
%% succesfully encoded. It's not checked at diameter:add_transport/2
@@ -561,6 +567,12 @@ recv(Bin, S) ->
%% recv1/3
+recv1(_,
+ #diameter_packet{header = H, bin = Bin},
+ #state{incoming_maxlen = M})
+ when M < size(Bin) ->
+ invalid(false, incoming_maxlen_exceeded, {size(Bin), H});
+
%% Incoming request after outgoing DPR: discard. Don't discard DPR, so
%% both ends don't do so when sending simultaneously.
recv1(Name,
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index a01bcdd4e7..86e744dfbe 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -131,7 +131,8 @@
| {share_peers, diameter:remotes()} %% broadcast to
| {use_shared_peers, diameter:remotes()} %% use from
| {restrict_connections, diameter:restriction()}
- | {string_decode, boolean()}]}).
+ | {string_decode, boolean()}
+ | {incoming_maxlen, diameter:message_length()}]}).
%% shared_peers reflects the peers broadcast from remote nodes.
%% Record representing an RFC 3539 watchdog process implemented by
@@ -698,7 +699,8 @@ service_options(Opts) ->
Opts,
?RESTRICT)},
{spawn_opt, proplists:get_value(spawn_opt, Opts, [])},
- {string_decode, proplists:get_value(string_decode, Opts, true)}].
+ {string_decode, proplists:get_value(string_decode, Opts, true)},
+ {incoming_maxlen, proplists:get_value(incoming_maxlen, Opts, 16#FFFFFF)}].
%% The order of options is significant since we match against the list.
mref(false = No) ->
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 784f9ca08f..ffd2c0afa2 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -78,7 +78,11 @@
service_name :: diameter:service_name(),
apps :: [#diameter_app{}],
sequence :: diameter:sequence(),
- codec :: list()}).
+ codec :: [{string_decode, boolean()}
+ | {incoming_maxlen, diameter:message_length()}]}).
+%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
+%% so that any message exceeding the maximum is discarded. Retain the
+%% option in case we want to extend the values and semantics.
%% Record stored in diameter_request for each outgoing request.
-record(request,
@@ -102,7 +106,9 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
peerT = PeerT,
apps = Apps,
sequence = Mask,
- codec = [T || {K,_} = T <- SvcOpts, K == string_decode]}.
+ codec = [T || {K,_} = T <- SvcOpts,
+ lists:member(K, [string_decode,
+ incoming_maxlen])]}.
%% ---------------------------------------------------------------------------
%% peer_up/1
@@ -233,6 +239,8 @@ receive_message(TPid, Pkt, Dict0, RecvData)
Dict0,
RecvData).
+%% recv/6
+
%% Incoming request ...
recv(true, false, TPid, Pkt, Dict0, T) ->
spawn_request(TPid, Pkt, Dict0, T);
@@ -240,6 +248,7 @@ recv(true, false, TPid, Pkt, Dict0, T) ->
%% ... answer to known request ...
recv(false, #request{ref = Ref, handler = Pid} = Req, _, Pkt, Dict0, _) ->
Pid ! {answer, Ref, Req, Dict0, Pkt};
+
%% Note that failover could have happened prior to this message being
%% received and triggering failback. That is, both a failover message
%% and answer may be on their way to the handler process. In the worst
@@ -971,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps,
session_id(Code, Vid, Dict0, Avps)
when is_list(Avps) ->
try
- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps),
- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}]
+ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
+ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}]
catch
error: _ ->
[]
@@ -989,26 +998,17 @@ failed_avp(_, [] = No) ->
%% find_avp/3
-find_avp(Code, Vid, Avps)
- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) ->
- find(fun(A) -> is_avp(Code, Vid, A) end, Avps).
+%% Grouped ...
+find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As
+ | _]) ->
+ As;
-%% The final argument here could be a list of AVP's, depending on the case,
-%% but we're only searching at the top level.
-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) ->
- true;
-is_avp(_, _, _) ->
- false.
+%% ... or not.
+find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) ->
+ A;
-find(_, []) ->
- false;
-find(Pred, [H|T]) ->
- case Pred(H) of
- true ->
- {value, H};
- false ->
- find(Pred, T)
- end.
+find_avp(Code, VId, [_ | Avps]) ->
+ find_avp(Code, VId, Avps).
%% 7. Error Handling
%%
@@ -1077,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) ->
incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
#diameter_packet{header = #diameter_header{is_error = E}
= Hdr,
- msg = Msg,
errors = Es}
= Pkt,
@@ -1087,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
%% Exit on a missing result code.
- T = rc_counter(Dict, Msg),
+ T = rc_counter(Dict, Dir, Pkt),
T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
- {Ctr, RC} = T,
+ {Ctr, RC, Avp} = T,
%% Or on an inappropriate value.
is_result(RC, E, Dict0)
- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
+ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}),
incr(TPid, {Id, Dir, Ctr}),
Ctr.
@@ -1107,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) ->
%% there are 2^32 (application ids) * 2^24 (command codes) = 2^56
%% pairs for an attacker to choose from.
msg_id(Hdr, Dict) ->
- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr),
- case Dict:msg_name(Code, 0 == R) of
- '' ->
- unknown(Dict:id(), R);
- _ ->
- Id
+ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr),
+ if Aid == ?APP_ID_RELAY ->
+ {relay, R};
+ true ->
+ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R),
+ unknown,
+ Id)
end.
-unknown(?APP_ID_RELAY, R) ->
- {relay, R};
-unknown(_, _) ->
- unknown.
-
%% No E-bit: can't be 3xxx.
is_result(RC, false, _Dict0) ->
RC < 3000 orelse 4000 =< RC;
@@ -1139,7 +1134,7 @@ is_result(RC, true, _) ->
incr(TPid, Counter) ->
diameter_stats:incr(Counter, TPid, 1).
-%% rc_counter/2
+%% rc_counter/3
%% RFC 3588, 7.6:
%%
@@ -1147,39 +1142,45 @@ incr(TPid, Counter) ->
%% applications MUST include either one Result-Code AVP or one
%% Experimental-Result AVP.
-rc_counter(Dict, Msg) ->
- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
+rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) ->
+ rc_counter(Dict, [H|As]);
-rcc(Dict, Msg, undefined) ->
- rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+rc_counter(Dict, _, #diameter_packet{msg = Msg}) ->
+ rc_counter(Dict, Msg).
-rcc(_, _, N)
+rc_counter(Dict, Msg) ->
+ rcc(get_result(Dict, Msg)).
+
+rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A)
when is_integer(N) ->
- {{'Result-Code', N}, N}.
+ {{Name, N}, N, A};
-%% Outgoing answers may be in any of the forms messages can be sent
-%% in. Incoming messages will be records. We're assuming here that the
-%% arity of the result code AVP's is 0 or 1.
+rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A)
+ when is_integer(N) ->
+ {{Name, N}, N, A};
-rcc([{_,_,N} = T | _])
+rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A)
when is_integer(N) ->
- {T,N};
-rcc({_,_,N} = T)
+ {T, N, A};
+
+rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A)
when is_integer(N) ->
- {T,N};
+ {T, N, A};
+
rcc(_) ->
false.
-%% Extract the first good looking integer. There's no guarantee
-%% that what we're looking for has arity 1.
-int([N|_])
- when is_integer(N) ->
- N;
-int(N)
- when is_integer(N) ->
- N;
-int(_) ->
- undefined.
+%% get_result/2
+
+get_result(Dict, Msg) ->
+ try
+ [throw(A) || N <- ['Result-Code', 'Experimental-Result'],
+ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]]
+ of
+ [] -> false
+ catch
+ #diameter_avp{} = A -> A
+ end.
x(T) ->
exit(T).
@@ -1519,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
%% a missing AVP. If both are optional in the dictionary
%% then this isn't a decode error: just continue on.
answer(Pkt, SvcName, App, Req);
- exit: {invalid_error_bit, {_, _, _, RC}} ->
+ exit: {invalid_error_bit, {_, _, _, Avp}} ->
#diameter_packet{errors = Es}
= Pkt,
- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}},
+ E = {5004, Avp},
answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req)
end.
@@ -1693,6 +1694,8 @@ send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) ->
end.
%% recv/4
+%%
+%% Relay an answer from a remote node.
recv(TPid, Pid, TRef, Ref) ->
receive
@@ -1857,7 +1860,7 @@ str([]) ->
str(T) ->
T.
-%% get_avp_value/3
+%% get_avp/3
%%
%% Find an AVP in a message of one of three forms:
%%
@@ -1874,47 +1877,71 @@ str(T) ->
%% look for are in the common dictionary. This is required since the
%% relay dictionary doesn't inherit the common dictionary (which maybe
%% it should).
-get_avp_value(?RELAY, Name, Msg) ->
- get_avp_value(?BASE, Name, Msg);
+get_avp(?RELAY, Name, Msg) ->
+ get_avp(?BASE, Name, Msg);
-%% Message sent as a header/avps list, probably a relay case but not
-%% necessarily.
-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
+%% Message as a header/avps list.
+get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
try
{Code, _, VId} = Dict:avp_header(Name),
- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) ->
- C /= Code orelse V /= VId
- end,
- Avps),
- avp_decode(Dict, Name, A)
+ find_avp(Code, VId, Avps)
+ of
+ A ->
+ avp_decode(Dict, Name, ungroup(A))
catch
error: _ ->
undefined
end;
%% Outgoing message as a name/values list.
-get_avp_value(_, Name, [_MsgName | Avps]) ->
+get_avp(_, Name, [_MsgName | Avps]) ->
case lists:keyfind(Name, 1, Avps) of
{_, V} ->
- V;
+ #diameter_avp{name = Name, value = V};
_ ->
undefined
end;
%% Message is typically a record but not necessarily.
-get_avp_value(Dict, Name, Rec) ->
+get_avp(Dict, Name, Rec) ->
try
- Dict:'#get-'(Name, Rec)
+ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
catch
error:_ ->
undefined
end.
+%% get_avp_value/3
+
+get_avp_value(Dict, Name, Msg) ->
+ case get_avp(Dict, Name, Msg) of
+ #diameter_avp{value = V} ->
+ V;
+ undefined = No ->
+ No
+ end.
+
+%% ungroup/1
+
+ungroup([Avp|_]) ->
+ Avp;
+ungroup(Avp) ->
+ Avp.
+
+%% avp_decode/3
+
avp_decode(Dict, Name, #diameter_avp{value = undefined,
- data = Bin}) ->
- Dict:avp(decode, Bin, Name);
-avp_decode(_, _, #diameter_avp{value = V}) ->
- V.
+ data = Bin}
+ = Avp) ->
+ try Dict:avp(decode, Bin, Name) of
+ V ->
+ Avp#diameter_avp{value = V}
+ catch
+ error:_ ->
+ Avp
+ end;
+avp_decode(_, _, #diameter_avp{} = Avp) ->
+ Avp.
cb(#diameter_app{module = [_|_] = M}, F, A) ->
eval(M, F, A);
diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl
index fe7613541c..87a0f0663d 100644
--- a/lib/diameter/src/base/diameter_types.erl
+++ b/lib/diameter/src/base/diameter_types.erl
@@ -93,7 +93,7 @@
case diameter_codec:getopt(string_decode) of
true ->
binary_to_list(Bin);
- _ ->
+ false ->
Bin
end;
@@ -565,18 +565,29 @@ msb(false) -> ?TIME_2036.
scan_uri(Bin) ->
RE = "^(aaas?)://"
- "([-a-zA-Z0-9.]+)"
- "(:([0-9]+))?"
+ "([-a-zA-Z0-9.]{1,255})"
+ "(:0{0,5}([0-9]{1,5}))?"
"(;transport=(tcp|sctp|udp))?"
"(;protocol=(diameter|radius|tacacs\\+))?$",
+ %% A port number is 16-bit, so an arbitrary number of digits is
+ %% just a vulnerability, but provide a little slack with leading
+ %% zeros in a port number just because the regexp was previously
+ %% [0-9]+ and it's not inconceivable that a value might be padded.
+ %% Don't fantasize about this padding being more than the number
+ %% of digits in the port number proper.
+ %%
+ %% Similarly, a FQDN can't be arbitrarily long: at most 255
+ %% octets.
{match, [A, DN, PN, T, P]} = re:run(Bin,
RE,
[{capture, [1,2,4,6,8], binary}]),
Type = to_atom(A),
{PN0, T0} = defaults(diameter_codec:getopt(rfc), Type),
+ PortNr = to_int(PN, PN0),
+ 0 = PortNr bsr 16, %% assert
#diameter_uri{type = Type,
- fqdn = from_bin(DN),
- port = to_int(PN, PN0),
+ fqdn = 'OctetString'(decode, DN),
+ port = PortNr,
transport = to_atom(T, T0),
protocol = to_atom(P, diameter)}.
@@ -588,14 +599,6 @@ defaults(6733, aaa) ->
defaults(6733, aaas) ->
{5658, tcp}.
-from_bin(B) ->
- case diameter_codec:getopt(string_decode) of
- true ->
- binary_to_list(B);
- false ->
- B
- end.
-
to_int(<<>>, N) ->
N;
to_int(B, _) ->
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index d91a776321..d5a9c81b09 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -183,7 +183,7 @@ erl_forms(Mod, ParseD) ->
f_enumerated_avp(ParseD),
f_empty_value(ParseD),
f_dict(ParseD),
- {eof, ?LINE}]],
+ {eof, erl_anno:new(?LINE)}]],
lists:append(Forms).
diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl
index dd03401b9e..04d5834c88 100644
--- a/lib/diameter/src/compiler/diameter_forms.hrl
+++ b/lib/diameter/src/compiler/diameter_forms.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -28,8 +28,10 @@
[],
[?APPLY(erlang, error, [?ATOM(badarg)])]}).
+-define(ANNO(L), erl_anno:new(L)).
+
%% Form tag with line number.
--define(F(T), T, ?LINE).
+-define(F(T), T, ?ANNO(?LINE)).
%% Yes, that's right. The replacement is to the first unmatched ')'.
-define(attribute, ?F(attribute)).
@@ -47,10 +49,10 @@
-define(record_index, ?F(record_index)).
-define(tuple, ?F(tuple)).
--define(ATOM(T), {atom, ?LINE, T}).
--define(INTEGER(N), {integer, ?LINE, N}).
--define(VAR(V), {var, ?LINE, V}).
--define(NIL, {nil, ?LINE}).
+-define(ATOM(T), {atom, ?ANNO(?LINE), T}).
+-define(INTEGER(N), {integer, ?ANNO(?LINE), N}).
+-define(VAR(V), {var, ?ANNO(?LINE), V}).
+-define(NIL, {nil, ?ANNO(?LINE)}).
-define(CALL(F,A), {?call, ?ATOM(F), A}).
-define(APPLY(M,F,A), {?call, {?remote, ?ATOM(M), ?ATOM(F)}, A}).
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index a54eb24031..0ef0fd35a9 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -35,32 +35,10 @@
{"1.4.3", [{restart_application, diameter}]}, %% R16B02
{"1.4.4", [{restart_application, diameter}]},
{"1.5", [{restart_application, diameter}]}, %% R16B03
- {"1.6", [{load_module, diameter_lib}, %% 17.0
- {load_module, diameter_traffic},
- {load_module, diameter_watchdog},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_service},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_relay},
- {load_module, diameter_codec},
- {load_module, diameter_sctp}]},
- {"1.7", [{load_module, diameter_service}, %% 17.1
- {load_module, diameter_codec},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_relay},
- {load_module, diameter_traffic},
- {load_module, diameter_peer_fsm}]},
- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3
- {load_module, diameter_watchdog},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_service}]},
- {"1.8", [{load_module, diameter_lib}, %% 17.4
+ {"1.6", [{restart_application, diameter}]}, %% 17.0
+ {"1.7", [{restart_application, diameter}]}, %% 17.[12]
+ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34]
+ [{load_module, diameter_lib},
{load_module, diameter_peer},
{load_module, diameter_reg},
{load_module, diameter_session},
@@ -84,7 +62,14 @@
{load_module, diameter_gen_relay},
{update, diameter_transport_sup, supervisor},
{update, diameter_service_sup, supervisor},
- {update, diameter_sup, supervisor}]}
+ {update, diameter_sup, supervisor}]},
+ {"1.9", [{load_module, diameter_codec}, %% 17.5
+ {load_module, diameter_traffic},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_relay}]}
],
[
{"0.9", [{restart_application, diameter}]},
@@ -102,32 +87,10 @@
{"1.4.3", [{restart_application, diameter}]},
{"1.4.4", [{restart_application, diameter}]},
{"1.5", [{restart_application, diameter}]},
- {"1.6", [{load_module, diameter_sctp},
- {load_module, diameter_codec},
- {load_module, diameter_gen_relay},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_service},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_watchdog},
- {load_module, diameter_traffic},
- {load_module, diameter_lib}]},
- {"1.7", [{load_module, diameter_peer_fsm},
- {load_module, diameter_traffic},
- {load_module, diameter_gen_relay},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_codec},
- {load_module, diameter_service}]},
- {"1.7.1", [{load_module, diameter_service},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_watchdog},
- {load_module, diameter_traffic}]},
- {"1.8", [{update, diameter_sup, supervisor},
+ {"1.6", [{restart_application, diameter}]},
+ {"1.7", [{restart_application, diameter}]},
+ {<<"^1\\.(7\\.1|8)$">>,
+ [{update, diameter_sup, supervisor},
{update, diameter_service_sup, supervisor},
{update, diameter_transport_sup, supervisor},
{load_module, diameter_gen_relay},
@@ -151,6 +114,13 @@
{load_module, diameter_session},
{load_module, diameter_reg},
{load_module, diameter_peer},
- {load_module, diameter_lib}]}
+ {load_module, diameter_lib}]},
+ {"1.9", [{load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_traffic},
+ {load_module, diameter_codec}]}
]
}.
diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl
index 071b1a1177..44fc3a60aa 100644
--- a/lib/diameter/test/diameter_3xxx_SUITE.erl
+++ b/lib/diameter/test/diameter_3xxx_SUITE.erl
@@ -47,6 +47,7 @@
send_double_error/1,
send_3xxx/1,
send_5xxx/1,
+ counters/1,
stop/1]).
%% diameter callbacks
@@ -111,7 +112,7 @@ all() ->
groups() ->
Tc = tc(),
- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]}
+ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]}
|| E <- ?ERRORS, D <- ?RFCS].
init_per_suite(Config) ->
@@ -169,6 +170,203 @@ stop(_Config) ->
ok = diameter:stop_service(?SERVER),
ok = diameter:stop_service(?CLIENT).
+%% counters/1
+%%
+%% Check that counters are as expected.
+
+counters(Config) ->
+ Group = proplists:get_value(group, Config),
+ [_Errors, _Rfc] = G = ?util:name(Group),
+ [] = ?util:run([[fun counters/3, K, S, G]
+ || K <- [statistics, transport, connections],
+ S <- [?CLIENT, ?SERVER]]).
+
+counters(Key, Svc, Group) ->
+ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)).
+
+counters(statistics, Svc, [Errors, Rfc], L) ->
+ [{P, Stats}] = L,
+ true = is_pid(P),
+ stats(Svc, Errors, Rfc, lists:sort(Stats));
+
+counters(_, _, _, _) ->
+ todo.
+
+stats(?CLIENT, E, rfc3588, L)
+ when E == answer;
+ E == answer_3xxx ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},6},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},1}]
+ = L;
+
+stats(?SERVER, E, rfc3588, L)
+ when E == answer;
+ E == answer_3xxx ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},6},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, answer, rfc6733, L) ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},3},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, answer, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},3},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, answer_3xxx, rfc6733, L) ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},2},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, answer_3xxx, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},2},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, callback, rfc3588, L) ->
+ [{{unknown,recv},1},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},6},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},2}]
+ = L;
+
+stats(?SERVER, callback, rfc3588, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},1},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},6},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},2},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, callback, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},3},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, callback, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},1},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},3},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L.
+
%% send_unknown_application/1
%%
%% Send an unknown application that a callback (which shouldn't take
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index 6975e83830..84f8a66a8a 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -249,11 +249,10 @@ release() ->
end.
unversion(App) ->
- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)),
- lists:reverse(case T of [$-|TT] -> TT; _ -> T end).
-
-is_vsn_ch(C) ->
- $0 =< C andalso C =< $9 orelse $. == C.
+ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App),
+ true = is_app(Name), %% assert
+ Vsn = vsn_str(Vsn), %%
+ Name.
app('$M_EXPR') -> %% could be anything but assume it's ok
"erts";
@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) ->
%% Write a rel file and return its name.
write_rel(Dir, [Erts | Apps], Vsn) ->
- true = is_vsn(Vsn),
- Name = "diameter_test_" ++ Vsn,
+ VS = vsn_str(Vsn),
+ Name = "diameter_test_" ++ VS,
ok = write_file(filename:join([Dir, Name ++ ".rel"]),
{release,
- {"diameter " ++ Vsn ++ " test release", Vsn},
+ {"diameter " ++ VS ++ " test release", VS},
Erts,
Apps}),
Name.
@@ -341,10 +340,34 @@ fetch(Key, List) ->
write_file(Path, T) ->
file:write_file(Path, io_lib:format("~p.", [T])).
-%% Is a version string of the expected form? Return the argument
-%% itself for 'false' for a useful badmatch.
+%% Is a version string of the expected form?
is_vsn(V) ->
- is_list(V)
- andalso length(V) == string:span(V, "0123456789.")
- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".."
- orelse {error, V}.
+ V = vsn_str(V),
+ true.
+
+%% Turn a from/to version in appup to a version string after ensuring
+%% that it's valid version number of regexp. In the regexp case, the
+%% regexp itself becomes the version string since there's no
+%% requirement that a version in appup be anything but a string. The
+%% restrictions placed on string-valued version numbers (that they be
+%% '.'-separated integers) are our own.
+
+vsn_str(S)
+ when is_list(S) ->
+ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")},
+ {_, nomatch} = {S, match(S, "\\.0\\.0$")},
+ S;
+
+vsn_str(B)
+ when is_binary(B) ->
+ {ok, _} = re:compile(B),
+ binary_to_list(B).
+
+match(S, RE) ->
+ re:run(S, RE, [{capture, none}]).
+
+%% Is an application name of the expected form?
+is_app(S)
+ when is_list(S) ->
+ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")},
+ true.
diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl
index 02501ce779..1c0f25864c 100644
--- a/lib/diameter/test/diameter_capx_SUITE.erl
+++ b/lib/diameter/test/diameter_capx_SUITE.erl
@@ -378,10 +378,14 @@ dict(N) ->
%% id's, failing with app_not_configured if it can't.
load_dict(N) ->
Mod = dict(N),
- Forms = [{attribute, 1, module, Mod},
- {attribute, 2, compile, [export_all]},
- {function, 3, id, 0,
- [{clause, 4, [], [], [{integer, 4, N}]}]}],
+ A1 = erl_anno:new(1),
+ A2 = erl_anno:new(2),
+ A3 = erl_anno:new(3),
+ A4 = erl_anno:new(4),
+ Forms = [{attribute, A1, module, Mod},
+ {attribute, A2, compile, [export_all]},
+ {function, A3, id, 0,
+ [{clause, A4, [], [], [{integer, A4, N}]}]}],
{ok, Mod, Bin, []} = compile:forms(Forms, [return]),
{module, Mod} = code:load_binary(Mod, Mod, Bin),
N = Mod:id().
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 854b71ba93..5f1dbfbd61 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -352,15 +352,19 @@ values('DiameterURI') ->
{[],
["aaa" ++ S ++ "://diameter.se" ++ P ++ Tr ++ Pr
|| S <- ["", "s"],
- P <- ["", ":1234"],
+ P <- ["", ":1234", ":0", ":65535"],
Tr <- ["" | [";transport=" ++ X
|| X <- ["tcp", "sctp", "udp"]]],
Pr <- ["" | [";protocol=" ++ X
|| X <- ["diameter","radius","tacacs+"]]],
Tr /= ";transport=udp"
- orelse (Pr /= ";protocol=diameter" andalso Pr /= "")],
- ["aaa://diameter.se;transport=udp;protocol=diameter",
+ orelse (Pr /= ";protocol=diameter" andalso Pr /= "")]
+ ++ ["aaa://" ++ lists:duplicate(255, $x)],
+ ["aaa://diameter.se:65536",
+ "aaa://diameter.se:-1",
+ "aaa://diameter.se;transport=udp;protocol=diameter",
"aaa://diameter.se;transport=udp",
+ "aaa://" ++ lists:duplicate(256, $x),
"aaa://:3868",
"aaax://diameter.se",
"aaa://diameter.se;transport=tcpx",
diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl
index 77f7aace1b..bbdf672291 100644
--- a/lib/diameter/test/diameter_config_SUITE.erl
+++ b/lib/diameter/test/diameter_config_SUITE.erl
@@ -85,6 +85,12 @@
{string_decode,
[[true], [false]],
[[0], [x]]},
+ {incoming_maxlen,
+ [[0], [65536], [16#FFFFFF]],
+ [[-1], [1 bsl 24], [infinity], [false]]},
+ {spawn_opt,
+ [[[]], [[monitor, link]]],
+ [[false]]},
{invalid_option, %% invalid service options are rejected
[],
[[x],
@@ -186,6 +192,9 @@
{private,
[[x]],
[]},
+ {spawn_opt,
+ [[[]], [[monitor, link]]],
+ [[false]]},
{invalid_option, %% invalid transport options are silently ignored
[[x],
[x,x]],
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 10c58ab6e7..7ff6ba7ab9 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -41,6 +41,7 @@
send_eval/1,
send_bad_answer/1,
send_protocol_error/1,
+ send_experimental_result/1,
send_arbitrary/1,
send_unknown/1,
send_unknown_short/1,
@@ -59,6 +60,7 @@
send_unexpected_mandatory_decode/1,
send_unexpected_mandatory/1,
send_long/1,
+ send_maxlen/1,
send_nopeer/1,
send_noapp/1,
send_discard/1,
@@ -182,6 +184,7 @@
{'Acct-Application-Id', [?DIAMETER_APP_ID_ACCOUNTING]},
{restrict_connections, false},
{string_decode, Decode},
+ {incoming_maxlen, 1 bsl 21},
{spawn_opt, [{min_heap_size, 5000}]}
| [{application, [{dictionary, D},
{module, ?MODULE},
@@ -299,6 +302,7 @@ tc() ->
send_eval,
send_bad_answer,
send_protocol_error,
+ send_experimental_result,
send_arbitrary,
send_unknown,
send_unknown_short,
@@ -317,6 +321,7 @@ tc() ->
send_unexpected_mandatory_decode,
send_unexpected_mandatory,
send_long,
+ send_maxlen,
send_nopeer,
send_noapp,
send_discard,
@@ -440,7 +445,7 @@ send_ok(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 1}],
- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
%% Send an accounting ACR that the server answers badly to.
@@ -456,7 +461,7 @@ send_eval(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 3}],
- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
%% Send an accounting ACR that the server tries to answer with an
@@ -477,12 +482,20 @@ send_protocol_error(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
+%% Send a 3xxx Experimental-Result in an answer not setting the E-bit
+%% and missing a Result-Code.
+send_experimental_result(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 5}],
+ ['ACA', {'Session-Id', _} | _]
+ = call(Config, Req).
+
%% Send an ASR with an arbitrary non-mandatory AVP and expect success
%% and the same AVP in the reply.
send_arbitrary(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name',
value = "XXX"}]}],
- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
{'AVP', [#diameter_avp{name = 'Product-Name',
value = V}]}
@@ -494,7 +507,7 @@ send_unknown(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = false,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
{'AVP', [#diameter_avp{code = 999,
is_mandatory = false,
@@ -510,7 +523,7 @@ send_unknown_short(Config, M, RC) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = M,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', RC} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -524,7 +537,7 @@ send_unknown_mandatory(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = true,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -544,7 +557,7 @@ send_unexpected_mandatory_decode(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout
is_mandatory = true,
data = <<12:32>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -580,7 +593,7 @@ send_error_bit(Config) ->
%% Send a bad version and check that we get 5011.
send_unsupported_version(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
= call(Config, Req).
%% Send a request containing an AVP length > data size.
@@ -600,14 +613,14 @@ send_zero_avp_length(Config) ->
send_invalid_avp_length(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId,
+ ['STA', {'Session-Id', _},
{'Result-Code', ?INVALID_AVP_LENGTH},
- _OriginHost,
- _OriginRealm,
- _UserName,
- _Class,
- _ErrorMessage,
- _ErrorReportingHost,
+ {'Origin-Host', _},
+ {'Origin-Realm', _},
+ {'User-Name', _},
+ {'Class', _},
+ {'Error-Message', _},
+ {'Error-Reporting-Host', _},
{'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]}
| _]
= call(Config, Req).
@@ -625,16 +638,22 @@ send_invalid_reject(Config) ->
send_unexpected_mandatory(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _]
= call(Config, Req).
%% Send something long that will be fragmented by TCP.
send_long(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT},
{'User-Name', [lists:duplicate(1 bsl 20, $X)]}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
+%% Send something longer than the configure incoming_maxlen.
+send_maxlen(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT},
+ {'User-Name', [lists:duplicate(1 bsl 21, $X)]}],
+ {timeout, _} = call(Config, Req).
+
%% Send something for which pick_peer finds no suitable peer.
send_nopeer(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
@@ -668,7 +687,7 @@ send_any_2(Config) ->
send_all_1(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM),
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [{host, any},
{realm, Realm}]}}]).
send_all_2(Config) ->
@@ -688,9 +707,8 @@ send_timeout(Config) ->
%% received the Session-Id.
send_error(Config) ->
Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}],
- ?answer_message(SId, ?TOO_BUSY)
- = call(Config, Req),
- true = undefined /= SId.
+ ?answer_message([_], ?TOO_BUSY)
+ = call(Config, Req).
%% Send a request with the detached option and receive it as a message
%% from handle_answer instead.
@@ -699,7 +717,7 @@ send_detach(Config) ->
Ref = make_ref(),
ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]),
Ans = receive {Ref, T} -> T end,
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= Ans.
%% Send a request which can't be encoded and expect {error, encode}.
@@ -712,11 +730,11 @@ send_destination_1(Config) ->
= group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
{'Destination-Host', [?HOST(SN, ?REALM)]}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [host, realm]}}]).
send_destination_2(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [host, realm]}}]).
%% Send with filtering on and expect failure when specifying an
@@ -780,7 +798,7 @@ send_bad_filter(Config, F) ->
%% Specify multiple filter options and expect them be conjunctive.
send_multiple_filters_1(Config) ->
Fun = fun(#diameter_caps{}) -> true end,
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= send_multiple_filters(Config, [host, {eval, Fun}]).
send_multiple_filters_2(Config) ->
E = {erlang, is_tuple, []},
@@ -791,7 +809,7 @@ send_multiple_filters_3(Config) ->
E2 = {erlang, is_tuple, []},
E3 = {erlang, is_record, [diameter_caps]},
E4 = [{erlang, is_record, []}, diameter_caps],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]).
send_multiple_filters(Config, Fs) ->
@@ -802,7 +820,7 @@ send_multiple_filters(Config, Fs) ->
%% only the return value from the prepare_request callback being
%% significant.
send_anything(Config) ->
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, anything).
%% ===========================================================================
@@ -1135,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
[R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)),
[Dict:rec2msg(R) | Vs].
+%% Missing Result-Codec and inapproriate Experimental-Result-Code.
+answer(Rec, Es, send_experimental_result) ->
+ [{5004, #diameter_avp{name = 'Experimental-Result'}},
+ {5005, #diameter_avp{name = 'Result-Code'}}]
+ = Es,
+ Rec;
+
%% An inappropriate E-bit results in a decode error ...
answer(Rec, Es, send_bad_answer) ->
[{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es,
@@ -1166,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) ->
%% Note that diameter will set Result-Code and Failed-AVPs if
%% #diameter_packet.errors is non-null.
-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
+handle_request(#diameter_packet{header = H, msg = M, avps = As},
+ _,
+ {_Ref, Caps}) ->
#diameter_header{end_to_end_id = EI,
hop_by_hop_id = HI}
= H,
@@ -1174,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
V = EI bsr B, %% assert
V = HI bsr B, %%
#diameter_caps{origin_state_id = {_,[Id]}} = Caps,
- answer(origin(Id), request(M, Caps)).
+ answer(origin(Id), request(M, [H|As], Caps)).
answer(T, {Tag, Action, Post}) ->
{Tag, answer(T, Action), Post};
+answer(_, {reply, [#diameter_header{} | _]} = T) ->
+ T;
answer({A,C}, {reply, Ans}) ->
answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)});
answer(pkt, {reply, Ans})
@@ -1186,6 +1215,41 @@ answer(pkt, {reply, Ans})
answer(_, T) ->
T.
+%% request/3
+
+%% send_experimental_result
+request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5},
+ [Hdr | Avps],
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}) ->
+ [H,R|T] = [A || N <- ['Origin-Host',
+ 'Origin-Realm',
+ 'Session-Id',
+ 'Accounting-Record-Type',
+ 'Accounting-Record-Number'],
+ #diameter_avp{} = A
+ <- [lists:keyfind(N, #diameter_avp.name, Avps)]],
+ Ans = [Hdr#diameter_header{is_request = false},
+ H#diameter_avp{data = OH},
+ R#diameter_avp{data = OR},
+ #diameter_avp{name = 'Experimental-Result',
+ code = 297,
+ need_encryption = false,
+ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON,
+ 'Vendor-Id',
+ 123}},
+ #diameter_avp{data
+ = {?DIAMETER_DICT_COMMON,
+ 'Experimental-Result-Code',
+ 3987}}]}
+ | T],
+ {reply, Ans};
+
+request(Msg, _Avps, Caps) ->
+ request(Msg, Caps).
+
+%% request/2
+
%% send_nok
request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0},
_) ->
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index c00bac26bb..db7f72c44e 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -16,5 +16,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 1.9
+DIAMETER_VSN = 1.9.1
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)