aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/diameter/doc/src/diameter.xml51
-rw-r--r--lib/diameter/doc/src/diameter_dict.xml9
-rw-r--r--lib/diameter/doc/src/seealso.ent5
-rw-r--r--lib/diameter/examples/code/client.erl16
-rw-r--r--lib/diameter/examples/code/relay.erl12
-rw-r--r--lib/diameter/examples/code/server.erl17
-rw-r--r--lib/diameter/src/base/diameter.erl3
-rw-r--r--lib/diameter/src/base/diameter_capx.erl40
-rw-r--r--lib/diameter/src/base/diameter_codec.erl88
-rw-r--r--lib/diameter/src/base/diameter_config.erl15
-rw-r--r--lib/diameter/src/base/diameter_peer.erl21
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl14
-rw-r--r--lib/diameter/src/base/diameter_service.erl52
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl71
-rw-r--r--lib/diameter/src/base/diameter_types.erl183
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl52
-rw-r--r--lib/diameter/test/diameter_codec_test.erl11
-rw-r--r--lib/diameter/test/diameter_config_SUITE.erl3
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl195
19 files changed, 630 insertions, 228 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index e22da3c9d5..bb281bc0dc 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -303,7 +303,7 @@ Defaults to <c>none</c>.</p>
<tag><c>{timeout, &dict_Unsigned32;}</c></tag>
<item>
<p>
-The number of milliseconds after which the request should
+Number of milliseconds after which the request should
timeout.
Defaults to 5000.</p>
</item>
@@ -742,7 +742,7 @@ info fields of forms other than the above.</p>
<tag><c>service_name() = term()</c></tag>
<item>
<p>
-The name of a service as passed to &start_service; and with which the
+Name of a service as passed to &start_service; and with which the
service is identified.
There can be at most one service with a given name on a given node.
Note that &make_ref;
@@ -754,7 +754,7 @@ can be used to generate a service name that is somewhat unique.</p>
<tag><c>service_opt()</c></tag>
<item>
<p>
-An option passed to &start_service;.
+Option passed to &start_service;.
Can be any <c>&capability;</c> as well as the following.</p>
<taglist>
@@ -762,7 +762,7 @@ Can be any <c>&capability;</c> as well as the following.</p>
<tag><c>{application, [&application_opt;]}</c></tag>
<item>
<p>
-Defines a Diameter application supported by the service.</p>
+A Diameter application supported by the service.</p>
<p>
A service must configure one tuple for each Diameter
@@ -790,7 +790,7 @@ be matched by corresponding &capability; configuration, of
| evaluable()}</c></tag>
<item>
<p>
-Specifies the degree to which the service allows multiple transport
+The degree to which the service allows multiple transport
connections to the same peer, as identified by its Origin-Host
at capabilities exchange.</p>
@@ -816,7 +816,7 @@ Defaults to <c>nodes</c>.</p>
<tag><c>{sequence, {H,N} | &evaluable;}</c></tag>
<item>
<p>
-Specifies a constant value <c>H</c> for the topmost <c>32-N</c> bits of
+A constant value <c>H</c> for the topmost <c>32-N</c> bits of
of 32-bit End-to-End and Hop-by-Hop Identifiers generated
by the service, either explicitly or as a return value of a function
to be evaluated at &start_service;.
@@ -851,7 +851,7 @@ outgoing requests.</p>
<tag><c>{share_peers, boolean() | [node()] | evaluable()}</c></tag>
<item>
<p>
-Specifies nodes to which peer connections established on the local
+Nodes to which peer connections established on the local
Erlang node are communicated.
Shared peers become available in the remote candidates list passed to
&app_pick_peer; callbacks on remote nodes whose services are
@@ -890,7 +890,7 @@ of a single Diameter node across multiple Erlang nodes.</p>
<tag><c>{spawn_opt, [term()]}</c></tag>
<item>
<p>
-An options list passed to &spawn_opt; when spawning a process for an
+Options list passed to &spawn_opt; when spawning a process for an
incoming Diameter request, unless the transport in question
specifies another value.
Options <c>monitor</c> and <c>link</c> are ignored.</p>
@@ -899,10 +899,34 @@ Options <c>monitor</c> and <c>link</c> are ignored.</p>
Defaults to the empty list.</p>
</item>
+<marker id="string_decode"/>
+<tag><c>{string_decode, boolean()}</c></tag>
+<item>
+<p>
+Whether or not to decode AVPs of type &dict_OctetString; and its
+derived types &dict_DiameterIdentity;, &dict_DiameterURI;,
+&dict_IPFilterRule;, &dict_QoSFilterRule;, and &dict_UTF8String;.
+If <c>true</c> then AVPs of these types are decoded to string().
+If <c>false</c> then values are retained as binary().</p>
+
+<p>
+Defaults to <c>true</c>.</p>
+
+<warning>
+<p>
+This option should be set to <c>false</c>
+since a sufficiently malicious peer can otherwise cause large amounts
+of memory to be consumed when decoded Diameter messages are passed
+between processes.
+The default value is for backwards compatibility.</p>
+</warning>
+
+</item>
+
<tag><c>{use_shared_peers, boolean() | [node()] | evaluable()}</c></tag>
<item>
<p>
-Specifies nodes from which communicated peers are made available in
+Nodes from which communicated peers are made available in
the remote candidates list of &app_pick_peer; callbacks.</p>
<p>
@@ -942,7 +966,7 @@ each node from which requests are sent.</p>
<tag><c>transport_opt()</c></tag>
<item>
<p>
-An option passed to &add_transport;.
+Option passed to &add_transport;.
Has one of the following types.</p>
<taglist>
@@ -950,8 +974,7 @@ Has one of the following types.</p>
<tag><c>{applications, [&application_alias;]}</c></tag>
<item>
<p>
-The list of Diameter applications to which the transport should be
-restricted.
+Diameter applications to which the transport should be restricted.
Defaults to all applications configured on the service in question.
Applications not configured on the service in question are ignored.</p>
@@ -984,7 +1007,7 @@ TLS is desired over TCP as implemented by &man_tcp;.</p>
<tag><c>{capabilities_cb, &evaluable;}</c></tag>
<item>
<p>
-A callback invoked upon reception of CER/CEA during capabilities
+Callback invoked upon reception of CER/CEA during capabilities
exchange in order to ask whether or not the connection should
be accepted.
Applied to the <c>&transport_ref;</c> and
@@ -1230,7 +1253,7 @@ the same peer.</p>
<tag><c>{spawn_opt, [term()]}</c></tag>
<item>
<p>
-Options list passed to &spawn_opt; when spawning a process for an
+Options passed to &spawn_opt; when spawning a process for an
incoming Diameter request.
Options <c>monitor</c> and <c>link</c> are ignored.</p>
diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml
index 810a146b88..9db9bcffde 100644
--- a/lib/diameter/doc/src/diameter_dict.xml
+++ b/lib/diameter/doc/src/diameter_dict.xml
@@ -529,6 +529,11 @@ 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
+for OctetString() and derived types, the representation is determined
+by the value of &mod_string_decode;.</p>
+
+<p>
<em>Basic AVP Data Formats</em></p>
<marker id="OctetString"/>
@@ -541,7 +546,7 @@ callback upon reception of an incoming request.</p>
<marker id="Grouped"/>
<pre>
-OctetString() = [0..255]
+OctetString() = string() | binary()
Integer32() = -2147483647..2147483647
Integer64() = -9223372036854775807..9223372036854775807
Unsigned32() = 0..4294967295
@@ -603,7 +608,7 @@ and <c>{{2104,2,26},{9,42,23}}</c> (both inclusive) can be encoded.</p>
<marker id="UTF8String"/>
<pre>
-UTF8String() = [integer()]
+UTF8String() = [integer()] | binary()
</pre>
<p>
diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent
index 8d87a4a5e7..4e205ffad7 100644
--- a/lib/diameter/doc/src/seealso.ent
+++ b/lib/diameter/doc/src/seealso.ent
@@ -70,6 +70,8 @@ significant.
<!ENTITY connect_timer '<seealso marker="#connect_timer">connect_timer</seealso>'>
<!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>'>
+
<!-- diameter_app -->
<!ENTITY app_handle_answer '<seealso marker="diameter_app#Mod:handle_answer-4">handle_answer/4</seealso>'>
@@ -103,6 +105,9 @@ significant.
<!ENTITY dict_Address '<seealso marker="diameter_dict#DATA_TYPES">Address()</seealso>'>
<!ENTITY dict_DiameterIdentity '<seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>'>
+<!ENTITY dict_DiameterURI '<seealso marker="diameter_dict#DATA_TYPES">DiameterURI()</seealso>'>
+<!ENTITY dict_IPFilterRule '<seealso marker="diameter_dict#DATA_TYPES">IPFilterRule()</seealso>'>
+<!ENTITY dict_QoSFilterRule '<seealso marker="diameter_dict#DATA_TYPES">QoSFilterRule()</seealso>'>
<!ENTITY dict_Grouped '<seealso marker="diameter_dict#DATA_TYPES">Grouped()</seealso>'>
<!ENTITY dict_OctetString '<seealso marker="diameter_dict#DATA_TYPES">OctetString()</seealso>'>
<!ENTITY dict_Time '<seealso marker="diameter_dict#DATA_TYPES">Time()</seealso>'>
diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl
index be5b4cbba5..844c9cdbdd 100644
--- a/lib/diameter/examples/code/client.erl
+++ b/lib/diameter/examples/code/client.erl
@@ -41,6 +41,7 @@
-include_lib("diameter/include/diameter_gen_base_rfc6733.hrl").
-export([start/1, %% start a service
+ start/2, %%
connect/2, %% add a connecting transport
call/1, %% send using the record encoding
cast/1, %% send using the list encoding and detached
@@ -68,6 +69,7 @@
{'Vendor-Id', 0},
{'Product-Name', "Client"},
{'Auth-Application-Id', [0]},
+ {string_decode, false},
{application, [{alias, common},
{dictionary, diameter_gen_base_rfc6733},
{module, client_cb}]}]).
@@ -76,11 +78,23 @@
start(Name)
when is_atom(Name) ->
- node:start(Name, ?SERVICE(Name)).
+ start(Name, []);
+
+start(Opts)
+ when is_list(Opts) ->
+ start(?DEF_SVC_NAME, Opts).
+
+%% start/0
start() ->
start(?DEF_SVC_NAME).
+%% start/2
+
+start(Name, Opts) ->
+ node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name),
+ false == lists:keymember(K, 1, Opts)]).
+
%% connect/2
connect(Name, T) ->
diff --git a/lib/diameter/examples/code/relay.erl b/lib/diameter/examples/code/relay.erl
index 0aa3cd06d3..7bc46dc68d 100644
--- a/lib/diameter/examples/code/relay.erl
+++ b/lib/diameter/examples/code/relay.erl
@@ -32,6 +32,7 @@
-module(relay).
-export([start/1,
+ start/2,
listen/2,
connect/2,
stop/1]).
@@ -49,6 +50,7 @@
{'Vendor-Id', 193},
{'Product-Name', "RelayAgent"},
{'Auth-Application-Id', [16#FFFFFFFF]},
+ {string_decode, false},
{application, [{alias, relay},
{dictionary, diameter_relay},
{module, relay_cb}]}]).
@@ -57,11 +59,19 @@
start(Name)
when is_atom(Name) ->
- node:start(Name, ?SERVICE(Name)).
+ start(Name, []).
+
+%% start/1
start() ->
start(?DEF_SVC_NAME).
+%% start/2
+
+start(Name, Opts) ->
+ node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name),
+ false == lists:keymember(K, 1, Opts)]).
+
%% listen/2
listen(Name, T) ->
diff --git a/lib/diameter/examples/code/server.erl b/lib/diameter/examples/code/server.erl
index 8c91e68895..f32cec594c 100644
--- a/lib/diameter/examples/code/server.erl
+++ b/lib/diameter/examples/code/server.erl
@@ -35,6 +35,7 @@
-module(server).
-export([start/1, %% start a service
+ start/2, %%
listen/2, %% add a listening transport
stop/1]). %% stop a service
@@ -53,6 +54,8 @@
{'Vendor-Id', 193},
{'Product-Name', "Server"},
{'Auth-Application-Id', [0]},
+ {restrict_connections, false},
+ {string_decode, false},
{application, [{alias, common},
{dictionary, diameter_gen_base_rfc6733},
{module, server_cb}]}]).
@@ -61,11 +64,23 @@
start(Name)
when is_atom(Name) ->
- node:start(Name, ?SERVICE(Name)).
+ start(Name, []);
+
+start(Opts)
+ when is_list(Opts) ->
+ start(?DEF_SVC_NAME, Opts).
+
+%% start/0
start() ->
start(?DEF_SVC_NAME).
+%% start/2
+
+start(Name, Opts) ->
+ node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name),
+ false == lists:keymember(K, 1, Opts)]).
+
%% listen/2
listen(Name, T) ->
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index da2695e1be..67dfc7bdbf 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.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
@@ -306,6 +306,7 @@ call(SvcName, App, Message) ->
| {restrict_connections, restriction()}
| {sequence, sequence() | evaluable()}
| {share_peers, remotes()}
+ | {string_decode, boolean()}
| {use_shared_peers, remotes()}
| {spawn_opt, list()}.
diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl
index 93548ecafd..7dc61f229f 100644
--- a/lib/diameter/src/base/diameter_capx.erl
+++ b/lib/diameter/src/base/diameter_capx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. 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
@@ -50,7 +50,8 @@
-export([build_CER/2,
recv_CER/3,
recv_CEA/3,
- make_caps/2]).
+ make_caps/2,
+ binary_caps/1]).
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
@@ -115,7 +116,8 @@ mk_caps(Caps0, Opts) ->
-define(SC(K,F),
set_cap({K, Val}, {Caps, #diameter_caps{F = false} = C}) ->
- {Caps#diameter_caps{F = cap(K, Val)}, C#diameter_caps{F = true}}).
+ {Caps#diameter_caps{F = cap(K, copy(Val))},
+ C#diameter_caps{F = true}}).
?SC('Origin-Host', origin_host);
?SC('Origin-Realm', origin_realm);
@@ -375,10 +377,10 @@ capx_to_caps(CEX, Dict) ->
'Firmware-Revision',
'AVP'],
CEX),
- #diameter_caps{origin_host = OH,
- origin_realm = OR,
+ #diameter_caps{origin_host = copy(OH),
+ origin_realm = copy(OR),
vendor_id = VId,
- product_name = PN,
+ product_name = copy(PN),
origin_state_id = OSI,
host_ip_address = IP,
supported_vendor_id = SV,
@@ -389,6 +391,32 @@ capx_to_caps(CEX, Dict) ->
firmware_revision = FR,
avp = X}.
+%% Copy binaries to avoid retaining a reference to a large binary
+%% containing AVPs we aren't interested in.
+copy(B)
+ when is_binary(B) ->
+ binary:copy(B);
+
+copy(T) ->
+ T.
+
+%% binary_caps/1
+%%
+%% Encode stringish capabilities with {string_decode, false}.
+
+binary_caps(Caps) ->
+ lists:foldl(fun bcaps/2, Caps, [#diameter_caps.origin_host,
+ #diameter_caps.origin_realm,
+ #diameter_caps.product_name]).
+
+bcaps(N, Caps) ->
+ case element(N, Caps) of
+ undefined ->
+ Caps;
+ V ->
+ setelement(N, Caps, iolist_to_binary(V))
+ end.
+
%% ---------------------------------------------------------------------------
%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index b4ecb63961..15a4c5e86f 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.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
@@ -22,6 +22,8 @@
-export([encode/2,
decode/2,
decode/3,
+ setopts/1,
+ getopt/1,
collect_avps/1,
decode_header/1,
sequence_numbers/1,
@@ -59,6 +61,50 @@
%% +-+-+-+-+-+-+-+-+-+-+-+-+-
%%% ---------------------------------------------------------------------------
+%%% # setopts/1
+%%% # getopt/1
+%%% ---------------------------------------------------------------------------
+
+%% These functions are a compromise in the same vein as the use of the
+%% process dictionary in diameter_gen.hrl in generated codec modules.
+%% Instead of rewriting the entire dictionary generation to pass
+%% encode/decode options around, the calling process sets them by
+%% calling setopts/1. At current, the only option is whether or not to
+%% decode binaries as strings, which is used by diameter_types.
+
+setopts(Opts)
+ when is_list(Opts) ->
+ lists:foreach(fun setopt/1, Opts).
+
+%% Decode stringish types to string()? The default true is for
+%% backwards compatibility.
+setopt({string_decode = K, false = B}) ->
+ setopt(K, B);
+
+%% Regard anything but the generated RFC 3588 dictionary as modern.
+%% This affects the interpretation of defaults during the decode
+%% of values of type DiameterURI, this having changed from RFC 3588.
+%% (So much for backwards compatibility.)
+setopt({common_dictionary, diameter_gen_base_rfc3588}) ->
+ setopt(rfc, 3588);
+
+setopt(_) ->
+ ok.
+
+setopt(Key, Value) ->
+ put({diameter, Key}, Value).
+
+getopt(Key) ->
+ case get({diameter, Key}) of
+ undefined when Key == string_decode ->
+ true;
+ undefined when Key == rfc ->
+ 6733;
+ V ->
+ V
+ end.
+
+%%% ---------------------------------------------------------------------------
%%% # encode/2
%%% ---------------------------------------------------------------------------
@@ -90,7 +136,7 @@ encode(Mod, Msg) ->
msg = Msg}).
e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
- try encode_avps(As) of
+ try encode_avps(reorder(As)) of
Avps ->
Length = size(Avps) + 20,
@@ -183,26 +229,50 @@ values(Avps) ->
%% Message as a list of #diameter_avp{} ...
encode_avps(_, _, [#diameter_avp{} | _] = Avps) ->
- encode_avps(reorder(Avps, [], Avps));
+ encode_avps(reorder(Avps));
%% ... or as a tuple list or record.
encode_avps(Mod, MsgName, Values) ->
Mod:encode_avps(MsgName, Values).
%% reorder/1
+%%
+%% Reorder AVPs for the relay case using the index field of
+%% diameter_avp records. Decode populates this field in collect_avps
+%% and presents AVPs in reverse order. A relay then sends the reversed
+%% list with a Route-Record AVP prepended. The goal here is just to do
+%% lists:reverse/1 in Grouped AVPs and the outer list, but only in the
+%% case there are indexed AVPs at all, so as not to reverse lists that
+%% have been explicilty sent (unindexed, in the desired order) as a
+%% diameter_avp list. The effect is the same as lists:keysort/2, but
+%% only on the cases we expect, not a general sort.
+
+reorder(Avps) ->
+ case reorder(Avps, []) of
+ false ->
+ Avps;
+ Sorted ->
+ Sorted
+ end.
-reorder([#diameter_avp{index = 0} | _] = Avps, Acc, _) ->
+%% reorder/3
+
+%% In case someone has reversed the list already. (Not likely.)
+reorder([#diameter_avp{index = 0} | _] = Avps, Acc) ->
Avps ++ Acc;
-reorder([#diameter_avp{index = N} = A | Avps], Acc, _)
+%% Assume indexed AVPs are in reverse order.
+reorder([#diameter_avp{index = N} = A | Avps], Acc)
when is_integer(N) ->
lists:reverse(Avps, [A | Acc]);
-reorder([H | T], Acc, Avps) ->
- reorder(T, [H | Acc], Avps);
+%% An unindexed AVP.
+reorder([H | T], Acc) ->
+ reorder(T, [H | Acc]);
-reorder([], Acc, _) ->
- Acc.
+%% No indexed members.
+reorder([], _) ->
+ false.
%% encode_avps/1
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index a89831aa5e..0d0304bf33 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -645,13 +645,23 @@ make_config(SvcName, Opts) ->
{false, monitor},
{?NOMASK, sequence},
{nodes, restrict_connections},
+ {true, string_decode},
{[], spawn_opt}]),
+ D = proplists:get_value(string_decode, SvcOpts, true),
+
#service{name = SvcName,
rec = #diameter_service{applications = Apps,
- capabilities = Caps},
+ capabilities = binary_caps(Caps, D)},
options = SvcOpts}.
+binary_caps(Caps, true) ->
+ Caps;
+binary_caps(Caps, false) ->
+ diameter_capx:binary_caps(Caps).
+
+%% make_opts/2
+
make_opts(Opts, Defs) ->
Known = [{K, get_opt(K, Opts, D)} || {D,K} <- Defs],
Unknown = Opts -- Known,
@@ -670,7 +680,8 @@ opt(K, false = B)
opt(K, true = B)
when K == share_peers;
- K == use_shared_peers ->
+ K == use_shared_peers;
+ K == string_decode ->
B;
opt(restrict_connections, T)
diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl
index ea326dd03e..89b63c8a92 100644
--- a/lib/diameter/src/base/diameter_peer.erl
+++ b/lib/diameter/src/base/diameter_peer.erl
@@ -232,12 +232,22 @@ recv(Pid, Pkt) ->
%% # send/2
%% ---------------------------------------------------------------------------
-send(Pid, #diameter_packet{transport_data = undefined,
- bin = Bin}) ->
- send(Pid, Bin);
+send(Pid, Msg) ->
+ ifc_send(Pid, {send, strip(Msg)}).
-send(Pid, Pkt) ->
- ifc_send(Pid, {send, Pkt}).
+%% Send only binary when possible.
+strip(#diameter_packet{transport_data = undefined,
+ bin = Bin}) ->
+ Bin;
+
+%% Strip potentially large message terms.
+strip(#diameter_packet{transport_data = T,
+ bin = Bin}) ->
+ #diameter_packet{transport_data = T,
+ bin = Bin};
+
+strip(Msg) ->
+ Msg.
%% ---------------------------------------------------------------------------
%% # close/1
@@ -326,7 +336,6 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ---------------------------------------------------------
-%% INTERNAL FUNCTIONS
%% ---------------------------------------------------------
%% ifc_send/2
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index 4ad4c346f4..aac2685514 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -154,7 +154,8 @@
%% # start/3
%% ---------------------------------------------------------------------------
--spec start(T, [Opt], {diameter:sequence(),
+-spec start(T, [Opt], {[diameter:service_opt()]
+ | diameter:sequence(), %% from old code
[node()],
module(),
#diameter_service{}})
@@ -193,10 +194,15 @@ init(T) ->
proc_lib:init_ack({ok, self()}),
gen_server:enter_loop(?MODULE, [], i(T)).
-i({Ack, WPid, {M, Ref} = T, Opts, {Mask, Nodes, Dict0, Svc}}) ->
+i({Ack, WPid, T, Opts, {{_,_} = Mask, Nodes, Dict0, Svc}}) -> %% from old code
+ i({Ack, WPid, T, Opts, {[{sequence, Mask}], Nodes, Dict0, Svc}});
+
+i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
erlang:monitor(process, WPid),
wait(Ack, WPid),
diameter_stats:reg(Ref),
+ diameter_codec:setopts([{common_dictionary, Dict0} | SvcOpts]),
+ {_,_} = Mask = proplists:get_value(sequence, SvcOpts),
{[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]),
putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}),
putr(?DPR_KEY, [F || {_, F} <- Ds]),
@@ -814,6 +820,8 @@ build_answer('CER',
= Pkt,
#state{dictionary = Dict0}
= S) ->
+ diameter_codec:setopts([{string_decode, false}]),
+
{SupportedApps, RCaps, CEA} = recv_CER(CER, S),
[RC, IS] = Dict0:'#get-'(['Result-Code', 'Inband-Security-Id'], CEA),
@@ -1022,6 +1030,8 @@ handle_CEA(#diameter_packet{header = H}
= DPkt
= diameter_codec:decode(Dict0, Pkt),
+ diameter_codec:setopts([{string_decode, false}]),
+
RC = result_code(incr_rc(recv, DPkt, Dict0)),
{SApps, IS, RCaps} = recv_CEA(DPkt, S),
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 04401a3d87..a01bcdd4e7 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -130,7 +130,8 @@
:: [{sequence, diameter:sequence()} %% sequence mask
| {share_peers, diameter:remotes()} %% broadcast to
| {use_shared_peers, diameter:remotes()} %% use from
- | {restrict_connections, diameter:restriction()}]}).
+ | {restrict_connections, diameter:restriction()}
+ | {string_decode, boolean()}]}).
%% shared_peers reflects the peers broadcast from remote nodes.
%% Record representing an RFC 3539 watchdog process implemented by
@@ -261,16 +262,22 @@ whois(SvcName) ->
%% ---------------------------------------------------------------------------
-spec pick_peer(SvcName, AppOrAlias, Opts)
- -> {{TPid, Caps, App}, Mask}
- | false
- | {error, term()}
+ -> {{TPid, Caps, App}, Mask, SvcOpts}
+ | false %% no selection
+ | {error, no_service}
when SvcName :: diameter:service_name(),
- AppOrAlias :: {alias, diameter:app_alias()} | #diameter_app{},
- Opts :: tuple(),
+ AppOrAlias :: #diameter_app{}
+ | {alias, diameter:app_alias()},
+ Opts :: {fun((Dict :: module()) -> [term()]),
+ diameter:peer_filter(),
+ Xtra :: list()},
TPid :: pid(),
Caps :: #diameter_caps{},
App :: #diameter_app{},
- Mask :: diameter:sequence().
+ Mask :: diameter:sequence(),
+ SvcOpts :: [diameter:service_opt()].
+%% Extract Mask in the returned tuple so that diameter_traffic doesn't
+%% need to know about the ordering of SvcOpts used here.
pick_peer(SvcName, App, Opts) ->
pick(lookup_state(SvcName), App, Opts).
@@ -287,10 +294,10 @@ pick(#state{service = #diameter_service{applications = Apps}}
Opts) -> %% initial call from diameter:call/4
pick(S, find_outgoing_app(Alias, Apps), Opts);
-pick(_, false, _) ->
- false;
+pick(_, false = No, _) ->
+ No;
-pick(#state{options = [{_, Mask} | _]}
+pick(#state{options = [{_, Mask} | SvcOpts]}
= S,
#diameter_app{module = ModX, dictionary = Dict}
= App0,
@@ -299,7 +306,7 @@ pick(#state{options = [{_, Mask} | _]}
[_,_] = RealmAndHost = diameter_lib:eval([DestF, Dict]),
case pick_peer(App, RealmAndHost, Filter, S) of
{TPid, Caps} ->
- {{TPid, Caps, App}, Mask};
+ {{TPid, Caps, App}, Mask, SvcOpts};
false = No ->
No
end.
@@ -690,7 +697,8 @@ service_options(Opts) ->
{restrict_connections, proplists:get_value(restrict_connections,
Opts,
?RESTRICT)},
- {spawn_opt, proplists:get_value(spawn_opt, Opts, [])}].
+ {spawn_opt, proplists:get_value(spawn_opt, Opts, [])},
+ {string_decode, proplists:get_value(string_decode, Opts, true)}].
%% The order of options is significant since we match against the list.
mref(false = No) ->
@@ -802,10 +810,13 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
when Type == connect;
Type == accept ->
#diameter_service{applications = Apps}
- = Svc
+ = Svc1
= merge_service(Opts, Svc0),
- {_,_} = Mask = proplists:get_value(sequence, SvcOpts),
- RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, Mask]),
+ Svc = binary_caps(Svc1, proplists:get_value(string_decode, SvcOpts, true)),
+ RecvData = diameter_traffic:make_recvdata([SvcName,
+ PeerT,
+ Apps,
+ SvcOpts]),
T = {{spawn_opts([Opts, SvcOpts]), RecvData}, Opts, SvcOpts, Svc},
Rec = #watchdog{type = Type,
ref = Ref,
@@ -816,8 +827,13 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
[],
N).
+binary_caps(Svc, true) ->
+ Svc;
+binary_caps(#diameter_service{capabilities = Caps} = Svc, false) ->
+ Svc#diameter_service{capabilities = diameter_capx:binary_caps(Caps)}.
+
wd(Type, Ref, T, WatchdogT, Rec) ->
- Pid = wd(Type, Ref, T),
+ Pid = start_watchdog(Type, Ref, T),
insert(WatchdogT, Rec#watchdog{pid = Pid}),
Pid.
@@ -831,7 +847,7 @@ spawn_opts(Optss) ->
T /= link,
T /= monitor].
-wd(Type, Ref, T) ->
+start_watchdog(Type, Ref, T) ->
{_MRef, Pid} = diameter_watchdog:start({Type, Ref}, T),
Pid.
@@ -852,7 +868,7 @@ ms({applications, As}, #diameter_service{applications = Apps} = S)
%% The fact that all capabilities can be configured on the transports
%% means that the service doesn't necessarily represent a single
-%% locally implemented Diameter peer as identified by Origin-Host: a
+%% locally implemented Diameter node as identified by Origin-Host: a
%% transport can configure its own Origin-Host. This means that the
%% service little more than a placeholder for default capabilities
%% plus a list of applications that individual transports can choose
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 0b503338a6..784f9ca08f 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-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
@@ -77,7 +77,8 @@
{peerT :: ets:tid(),
service_name :: diameter:service_name(),
apps :: [#diameter_app{}],
- sequence :: diameter:sequence()}).
+ sequence :: diameter:sequence(),
+ codec :: list()}).
%% Record stored in diameter_request for each outgoing request.
-record(request,
@@ -92,11 +93,16 @@
%% # make_recvdata/1
%% ---------------------------------------------------------------------------
-make_recvdata([SvcName, PeerT, Apps, Mask | _]) ->
+make_recvdata([SvcName, PeerT, Apps, {_,_} = Mask | _]) -> %% from old code
+ make_recvdata([SvcName, PeerT, Apps, [{sequence, Mask}]]);
+
+make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
+ {_,_} = Mask = proplists:get_value(sequence, SvcOpts),
#recvdata{service_name = SvcName,
peerT = PeerT,
apps = Apps,
- sequence = Mask}.
+ sequence = Mask,
+ codec = [T || {K,_} = T <- SvcOpts, K == string_decode]}.
%% ---------------------------------------------------------------------------
%% peer_up/1
@@ -270,8 +276,11 @@ recv_request(TPid,
#diameter_packet{header = #diameter_header{application_id = Id}}
= Pkt,
Dict0,
- #recvdata{peerT = PeerT, apps = Apps}
+ #recvdata{peerT = PeerT,
+ apps = Apps,
+ codec = Opts}
= RecvData) ->
+ diameter_codec:setopts([{common_dictionary, Dict0} | Opts]),
send_A(recv_R(diameter_service:find_incoming_app(PeerT, TPid, Id, Apps),
TPid,
Pkt,
@@ -279,7 +288,13 @@ recv_request(TPid,
RecvData),
TPid,
Dict0,
- RecvData).
+ RecvData);
+
+recv_request(TPid, Pkt, Dict0, RecvData) -> %% from old code
+ recv_request(TPid,
+ Pkt,
+ Dict0,
+ #recvdata{} = erlang:append_element(RecvData, [])).
%% recv_R/5
@@ -596,7 +611,7 @@ resend(false,
Route = #diameter_avp{data = {Dict0, 'Route-Record', OH}},
Seq = diameter_session:sequence(Mask),
Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq},
- Msg = [Hdr, Route | Avps],
+ Msg = [Hdr, Route | Avps], %% reordered at encode
resend(send_request(SvcName, App, Msg, Opts), Caps, Dict0, Pkt).
%% The incoming request is relayed with the addition of a
%% Route-Record. Note the requirement on the return from call/4 below,
@@ -1225,10 +1240,9 @@ answer_rc(_, _, Sent) ->
send_R(SvcName, AppOrAlias, Msg, Opts, Caller) ->
case pick_peer(SvcName, AppOrAlias, Msg, Opts) of
- {{_,_,_} = Transport, Mask} ->
+ {Transport, Mask, SvcOpts} ->
+ diameter_codec:setopts(SvcOpts),
send_request(Transport, Mask, Msg, Opts, Caller, SvcName);
- false ->
- {error, no_connection};
{error, _} = No ->
No
end.
@@ -1290,6 +1304,8 @@ send_request({TPid, Caps, App}
SvcName,
[]).
+%% send_R/7
+
send_R({send, Msg}, Pkt, Transport, Opts, Caller, SvcName, Fs) ->
send_R(make_request_packet(Msg, Pkt),
Transport,
@@ -1550,7 +1566,9 @@ a(Hdr, SvcName, discard) ->
%% timer value is ignored. This means that an answer could be accepted
%% from a peer after timeout in the case of failover.
-retransmit({{_,_,App} = Transport, _Mask}, Req, Opts, SvcName, Timeout) ->
+%% retransmit/5
+
+retransmit({{_,_,App} = Transport, _, _}, Req, Opts, SvcName, Timeout) ->
try retransmit(Transport, Req, SvcName, Timeout) of
T -> recv_A(Timeout, SvcName, App, Opts, T)
catch
@@ -1571,17 +1589,26 @@ pick_peer(SvcName,
pick_peer(SvcName, App, Msg, Opts#options{extra = []});
pick_peer(_, _, undefined, _) ->
- false;
+ {error, no_connection};
pick_peer(SvcName,
AppOrAlias,
Msg,
#options{filter = Filter, extra = Xtra}) ->
- diameter_service:pick_peer(SvcName,
- AppOrAlias,
- {fun(D) -> get_destination(D, Msg) end,
- Filter,
- Xtra}).
+ pick(diameter_service:pick_peer(SvcName,
+ AppOrAlias,
+ {fun(D) -> get_destination(D, Msg) end,
+ Filter,
+ Xtra})).
+
+pick({{_,_,_} = Transport, Mask}) -> %% from old code; dialyzer complains
+ {Transport, Mask, []}; %% about this
+
+pick(false) ->
+ {error, no_connection};
+
+pick(T) ->
+ T.
%% handle_error/4
@@ -1679,8 +1706,14 @@ recv(TPid, Pid, TRef, Ref) ->
%% send/2
-send(Pid, Pkt) ->
- Pid ! {send, Pkt}.
+send(Pid, Pkt) -> %% Strip potentially large message terms.
+ #diameter_packet{header = H,
+ bin = Bin,
+ transport_data = T}
+ = Pkt,
+ Pid ! {send, #diameter_packet{header = H,
+ bin = Bin,
+ transport_data = T}}.
%% retransmit/4
diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl
index 442d90c98b..fe7613541c 100644
--- a/lib/diameter/src/base/diameter_types.erl
+++ b/lib/diameter/src/base/diameter_types.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. 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
@@ -90,7 +90,12 @@
'OctetString'(decode, Bin)
when is_binary(Bin) ->
- binary_to_list(Bin);
+ case diameter_codec:getopt(string_decode) of
+ true ->
+ binary_to_list(Bin);
+ _ ->
+ Bin
+ end;
'OctetString'(decode, B) ->
?INVALID_LENGTH(B);
@@ -298,21 +303,29 @@
'OctetString'(M, lists:duplicate(0,7));
'DiameterURI'(encode, #diameter_uri{type = Type,
- fqdn = D,
- port = P,
+ fqdn = DN,
+ port = PN,
transport = T,
- protocol = Prot}
- = U) ->
- S = lists:append([atom_to_list(Type), "://", D,
- ":", integer_to_list(P),
+ protocol = P})
+ when (Type == 'aaa' orelse Type == 'aaas'),
+ is_integer(PN),
+ 0 =< PN,
+ (T == tcp orelse T == sctp orelse T == udp),
+ (P == diameter orelse P == radius orelse P == 'tacacs+'),
+ (P /= diameter orelse T /= udp) ->
+ iolist_to_binary([atom_to_list(Type), "://", DN,
+ ":", integer_to_list(PN),
";transport=", atom_to_list(T),
- ";protocol=", atom_to_list(Prot)]),
- U = scan_uri(S), %% assert
- list_to_binary(S);
+ ";protocol=", atom_to_list(P)]);
+%% Don't omit defaults since they're dependent on whether RFC 3588 or
+%% 6733 is being followed. For one, we don't know this at encode; for
+%% two (more importantly), we don't know how the peer will interpret
+%% defaults, so it's best to be explicit. Interpret defaults on decode
+%% since there's no choice.
'DiameterURI'(encode, Str) ->
Bin = iolist_to_binary(Str),
- #diameter_uri{} = scan_uri(Bin), %% type check
+ #diameter_uri{} = scan_uri(Bin), %% assert
Bin.
%% --------------------
@@ -321,7 +334,6 @@
'IPFilterRule'(encode = M, zero) ->
'OctetString'(M, lists:duplicate(0,33));
-%% TODO: parse grammar.
'IPFilterRule'(M, X) ->
'OctetString'(M, X).
@@ -331,7 +343,6 @@
'QoSFilterRule'(encode = M, zero = X) ->
'IPFilterRule'(M, X);
-%% TODO: parse grammar.
'QoSFilterRule'(M, X) ->
'OctetString'(M, X).
@@ -339,7 +350,13 @@
'UTF8String'(decode, Bin)
when is_binary(Bin) ->
- tl([0|_] = unicode:characters_to_list([0, Bin])); %% assert list return
+ case diameter_codec:getopt(string_decode) of
+ true ->
+ %% assert list return
+ tl([0|_] = unicode:characters_to_list([0, Bin]));
+ false ->
+ <<_/binary>> = unicode:characters_to_binary(Bin)
+ end;
'UTF8String'(decode, B) ->
?INVALID_LENGTH(B);
@@ -507,55 +524,87 @@ msb(false) -> ?TIME_2036.
%%
%% aaa-protocol = ( "diameter" / "radius" / "tacacs+" )
-scan_uri(Bin)
- when is_binary(Bin) ->
- scan_uri(binary_to_list(Bin));
-scan_uri("aaa://" ++ Rest) ->
- scan_fqdn(Rest, #diameter_uri{type = aaa});
-scan_uri("aaas://" ++ Rest) ->
- scan_fqdn(Rest, #diameter_uri{type = aaas}).
-
-scan_fqdn(S, U) ->
- {[_|_] = F, Rest} = lists:splitwith(fun is_fqdn/1, S),
- scan_opt_port(Rest, U#diameter_uri{fqdn = F}).
-
-scan_opt_port(":" ++ S, U) ->
- {[_|_] = P, Rest} = lists:splitwith(fun is_digit/1, S),
- scan_opt_transport(Rest, U#diameter_uri{port = list_to_integer(P)});
-scan_opt_port(S, U) ->
- scan_opt_transport(S, U).
-
-scan_opt_transport(";transport=" ++ S, U) ->
- {P, Rest} = transport(S),
- scan_opt_protocol(Rest, U#diameter_uri{transport = P});
-scan_opt_transport(S, U) ->
- scan_opt_protocol(S, U).
-
-scan_opt_protocol(";protocol=" ++ S, U) ->
- {P, ""} = protocol(S),
- U#diameter_uri{protocol = P};
-scan_opt_protocol("", U) ->
- U.
-
-transport("tcp" ++ S) ->
- {tcp, S};
-transport("sctp" ++ S) ->
- {sctp, S};
-transport("udp" ++ S) ->
- {udp, S}.
-
-protocol("diameter" ++ S) ->
- {diameter, S};
-protocol("radius" ++ S) ->
- {radius, S};
-protocol("tacacs+" ++ S) ->
- {'tacacs+', S}.
-
-is_fqdn(C) ->
- is_digit(C) orelse is_alpha(C) orelse C == $. orelse C == $-.
-
-is_alpha(C) ->
- ($a =< C andalso C =< $z) orelse ($A =< C andalso C =< $Z).
-
-is_digit(C) ->
- $0 =< C andalso C =< $9.
+%% RFC 6733, 4.3.1, changes the defaults:
+%%
+%% "aaa://" FQDN [ port ] [ transport ] [ protocol ]
+%%
+%% ; No transport security
+%%
+%% "aaas://" FQDN [ port ] [ transport ] [ protocol ]
+%%
+%% ; Transport security used
+%%
+%% FQDN = < Fully Qualified Domain Name >
+%%
+%% port = ":" 1*DIGIT
+%%
+%% ; One of the ports used to listen for
+%% ; incoming connections.
+%% ; If absent, the default Diameter port
+%% ; (3868) is assumed if no transport
+%% ; security is used and port 5658 when
+%% ; transport security (TLS/TCP and DTLS/SCTP)
+%% ; is used.
+%%
+%% transport = ";transport=" transport-protocol
+%%
+%% ; One of the transports used to listen
+%% ; for incoming connections. If absent,
+%% ; the default protocol is assumed to be TCP.
+%% ; UDP MUST NOT be used when the aaa-protocol
+%% ; field is set to diameter.
+%%
+%% transport-protocol = ( "tcp" / "sctp" / "udp" )
+%%
+%% protocol = ";protocol=" aaa-protocol
+%%
+%% ; If absent, the default AAA protocol
+%% ; is Diameter.
+%%
+%% aaa-protocol = ( "diameter" / "radius" / "tacacs+" )
+
+scan_uri(Bin) ->
+ RE = "^(aaas?)://"
+ "([-a-zA-Z0-9.]+)"
+ "(:([0-9]+))?"
+ "(;transport=(tcp|sctp|udp))?"
+ "(;protocol=(diameter|radius|tacacs\\+))?$",
+ {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),
+ #diameter_uri{type = Type,
+ fqdn = from_bin(DN),
+ port = to_int(PN, PN0),
+ transport = to_atom(T, T0),
+ protocol = to_atom(P, diameter)}.
+
+%% Choose defaults based on the RFC, since 6733 has changed them.
+defaults(3588, _) ->
+ {3868, sctp};
+defaults(6733, aaa) ->
+ {3868, tcp};
+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, _) ->
+ binary_to_integer(B).
+
+to_atom(<<>>, A) ->
+ A;
+to_atom(B, _) ->
+ to_atom(B).
+
+to_atom(B) ->
+ binary_to_atom(B, latin1).
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index ff51c6dcf7..de9c4bca33 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -124,14 +124,16 @@ i({Ack, T, Pid, {RecvData,
wait(Ack, Pid),
{_, Seed} = diameter_lib:seed(),
random:seed(Seed),
- putr(restart, {T, Opts, Svc}), %% save seeing it in trace
- putr(dwr, dwr(Caps)), %%
+ putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
+ putr(dwr, dwr(Caps)), %%
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
Restrict = proplists:get_value(restrict_connections, SvcOpts),
Nodes = restrict_nodes(Restrict),
Dict0 = common_dictionary(Apps),
+ diameter_codec:setopts([{common_dictionary, Dict0},
+ {string_decode, false}]),
#watchdog{parent = Pid,
- transport = start(T, Opts, Mask, Nodes, Dict0, Svc),
+ transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc),
tw = proplists:get_value(watchdog_timer,
Opts,
?DEFAULT_TW_INIT),
@@ -166,11 +168,11 @@ config({okay, N}, Rec)
when ?IS_NATURAL(N) ->
Rec#config{okay = N}.
-%% start/5
+%% start/6
-start(T, Opts, Mask, Nodes, Dict0, Svc) ->
+start(T, Opts, SvcOpts, Nodes, Dict0, Svc) ->
{_MRef, Pid}
- = diameter_peer_fsm:start(T, Opts, {Mask, Nodes, Dict0, Svc}),
+ = diameter_peer_fsm:start(T, Opts, {SvcOpts, Nodes, Dict0, Svc}),
Pid.
%% common_dictionary/1
@@ -320,7 +322,7 @@ code_change(_, State, _) ->
%% expiry; or another watchdog is saying the same after reestablishing
%% a connection previously had by this one.
transition(close, #watchdog{}) ->
- {{accept, _}, _, _} = getr(restart), %% assert
+ {accept, _} = role(), %% assert
stop;
%% Service is asking for the peer to be taken down gracefully.
@@ -370,7 +372,7 @@ transition({open, TPid, Hosts, _} = Open,
restrict = {_,R},
config = #config{suspect = OS}}
= S) ->
- case okay(getr(restart), Hosts, R) of
+ case okay(role(), Hosts, R) of
okay ->
set_watchdog(S#watchdog{status = okay,
num_dwa = OS});
@@ -424,7 +426,7 @@ transition({'DOWN', _, process, TPid, _Reason} = D,
= S0) ->
S = S0#watchdog{pending = false,
transport = undefined},
- {{M,_}, _, _} = getr(restart),
+ {M,_} = role(),
%% Close an accepting watchdog immediately if there's no
%% restriction on the number of connections to the same peer: the
@@ -491,7 +493,7 @@ encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD}
%% okay/3
-okay({{accept, Ref}, _, _}, Hosts, Restrict) ->
+okay({accept, Ref}, Hosts, Restrict) ->
T = {?MODULE, connection, Ref, Hosts},
diameter_reg:add(T),
if Restrict ->
@@ -502,7 +504,7 @@ okay({{accept, Ref}, _, _}, Hosts, Restrict) ->
%% Register before matching so that at least one of two registering
%% processes will match the other.
-okay({{connect, _}, _, _}, _, _) ->
+okay({connect, _}, _, _) ->
okay.
%% okay/2
@@ -517,6 +519,11 @@ okay(C) ->
[_|_] = [send(P, close) || {_,P} <- C, self() /= P],
reopen.
+%% role/0
+
+role() ->
+ element(1, getr(restart)).
+
%% set_watchdog/1
set_watchdog(#watchdog{tw = TwInit,
@@ -573,11 +580,18 @@ rcv('DWR', Pkt, #watchdog{transport = TPid,
DPkt = diameter_codec:decode(Dict0, Pkt),
diameter_traffic:incr(recv, DPkt, TPid, Dict0),
diameter_traffic:incr_error(recv, DPkt, TPid, Dict0),
- EPkt = encode(dwa, Dict0, Pkt),
+ #diameter_packet{header = H,
+ transport_data = T,
+ bin = Bin}
+ = EPkt
+ = encode(dwa, Dict0, Pkt),
diameter_traffic:incr(send, EPkt, TPid, Dict0),
diameter_traffic:incr_rc(send, EPkt, TPid, Dict0),
- send(TPid, {send, EPkt}),
+ %% Strip potentially large message terms.
+ send(TPid, {send, #diameter_packet{header = H,
+ transport_data = T,
+ bin = Bin}}),
?LOG(send, 'DWA');
rcv('DWA', Pkt, #watchdog{transport = TPid,
@@ -796,26 +810,28 @@ restart(S) -> %% reconnect has won race with timeout
%% state down rather then initial when receiving notification of an
%% open connection.
-restart({{connect, _} = T, Opts, Svc},
+restart({T, Opts, Svc}, S) -> %% put in old code
+ restart({T, Opts, Svc, []}, S);
+
+restart({{connect, _} = T, Opts, Svc, SvcOpts},
#watchdog{parent = Pid,
- sequence = Mask,
restrict = {R,_},
dictionary = Dict0}
= S) ->
send(Pid, {reconnect, self()}),
Nodes = restrict_nodes(R),
- S#watchdog{transport = start(T, Opts, Mask, Nodes, Dict0, Svc),
+ S#watchdog{transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc),
restrict = {R, lists:member(node(), Nodes)}};
%% No restriction on the number of connections to the same peer: just
%% die. Note that a state machine never enters state REOPEN in this
%% case.
-restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) ->
+restart({{accept, _}, _, _, _}, #watchdog{restrict = {_, false}}) ->
stop; %% 'DOWN' was in old code: 'close' was not sent
%% Otherwise hang around until told to die, either by the service or
%% by another watchdog.
-restart({{accept, _}, _, _}, S) ->
+restart({{accept, _}, _, _, _}, S) ->
S.
%% Don't currently use Opts/Svc in the accept case.
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 472755c62a..854b71ba93 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -356,8 +356,15 @@ values('DiameterURI') ->
Tr <- ["" | [";transport=" ++ X
|| X <- ["tcp", "sctp", "udp"]]],
Pr <- ["" | [";protocol=" ++ X
- || X <- ["diameter","radius","tacacs+"]]]],
- []};
+ || X <- ["diameter","radius","tacacs+"]]],
+ Tr /= ";transport=udp"
+ orelse (Pr /= ";protocol=diameter" andalso Pr /= "")],
+ ["aaa://diameter.se;transport=udp;protocol=diameter",
+ "aaa://diameter.se;transport=udp",
+ "aaa://:3868",
+ "aaax://diameter.se",
+ "aaa://diameter.se;transport=tcpx",
+ "aaa://diameter.se;transport=tcp;protocol=diameter "]};
values(T)
when T == 'IPFilterRule';
diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl
index 6779524b14..77f7aace1b 100644
--- a/lib/diameter/test/diameter_config_SUITE.erl
+++ b/lib/diameter/test/diameter_config_SUITE.erl
@@ -82,6 +82,9 @@
[false],
[[node(), node()]]],
[[x]]},
+ {string_decode,
+ [[true], [false]],
+ [[0], [x]]},
{invalid_option, %% invalid service options are rejected
[],
[[x],
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 9822b95301..10c58ab6e7 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -122,8 +122,6 @@
-define(ADDR, {127,0,0,1}).
--define(CLIENT, "CLIENT").
--define(SERVER, "SERVER").
-define(REALM, "erlang.org").
-define(HOST(Host, Realm), Host ++ [$.|Realm]).
@@ -141,11 +139,19 @@
%% Which common dictionary to use in the clients.
-define(RFCS, [rfc3588, rfc6733]).
+%% Whether to decode stringish Diameter types to strings, or leave
+%% them as binary.
+-define(STRING_DECODES, [true, false]).
+
-record(group,
- {client_encoding,
+ {client_service,
+ client_encoding,
client_dict0,
+ client_strings,
+ server_service,
server_encoding,
- server_container}).
+ server_container,
+ server_strings}).
%% Not really what we should be setting unless the message is sent in
%% the common application but diameter doesn't care.
@@ -166,7 +172,7 @@
?answer_message(_, ResultCode)).
%% Config for diameter:start_service/2.
--define(SERVICE(Name),
+-define(SERVICE(Name, Decode),
[{'Origin-Host', Name ++ "." ++ ?REALM},
{'Origin-Realm', ?REALM},
{'Host-IP-Address', [?ADDR]},
@@ -175,6 +181,7 @@
{'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]},
{'Acct-Application-Id', [?DIAMETER_APP_ID_ACCOUNTING]},
{restrict_connections, false},
+ {string_decode, Decode},
{spawn_opt, [{min_heap_size, 5000}]}
| [{application, [{dictionary, D},
{module, ?MODULE},
@@ -227,28 +234,53 @@ suite() ->
[{timetrap, {seconds, 60}}].
all() ->
- [start, start_services, add_transports, result_codes]
- ++ [{group, ?util:name([R,D,A,C]), P} || R <- ?ENCODINGS,
- D <- ?RFCS,
- A <- ?ENCODINGS,
- C <- ?CONTAINERS,
- P <- [[], [parallel]]]
- ++ [outstanding, remove_transports, empty, stop_services, stop].
+ [start, result_codes, {group, traffic}, outstanding, empty, stop].
groups() ->
Ts = tc(),
- [{?util:name([R,D,A,C]), [], Ts} || R <- ?ENCODINGS,
- D <- ?RFCS,
- A <- ?ENCODINGS,
- C <- ?CONTAINERS].
+ [{?util:name([R,D,A,C]), [parallel], Ts} || R <- ?ENCODINGS,
+ D <- ?RFCS,
+ A <- ?ENCODINGS,
+ C <- ?CONTAINERS]
+ ++
+ [{?util:name([R,D,A,C,SD,CD]),
+ [],
+ [start_services,
+ add_transports,
+ result_codes,
+ {group, ?util:name([R,D,A,C])},
+ remove_transports,
+ stop_services]}
+ || R <- ?ENCODINGS,
+ D <- ?RFCS,
+ A <- ?ENCODINGS,
+ C <- ?CONTAINERS,
+ SD <- ?STRING_DECODES,
+ CD <- ?STRING_DECODES]
+ ++
+ [{traffic, [parallel], [{group, ?util:name([R,D,A,C,SD,CD])}
+ || R <- ?ENCODINGS,
+ D <- ?RFCS,
+ A <- ?ENCODINGS,
+ C <- ?CONTAINERS,
+ SD <- ?STRING_DECODES,
+ CD <- ?STRING_DECODES]}].
init_per_group(Name, Config) ->
- [R,D,A,C] = ?util:name(Name),
- G = #group{client_encoding = R,
- client_dict0 = dict0(D),
- server_encoding = A,
- server_container = C},
- [{group, G} | Config].
+ case ?util:name(Name) of
+ [R,D,A,C,SD,CD] ->
+ G = #group{client_service = [$C|?util:unique_string()],
+ client_encoding = R,
+ client_dict0 = dict0(D),
+ client_strings = CD,
+ server_service = [$S|?util:unique_string()],
+ server_encoding = A,
+ server_container = C,
+ server_strings = SD},
+ [{group, G} | Config];
+ _ ->
+ Config
+ end.
end_per_group(_, _) ->
ok.
@@ -319,18 +351,26 @@ tc() ->
start(_Config) ->
ok = diameter:start().
-start_services(_Config) ->
- ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)),
- ok = diameter:start_service(?CLIENT, [{sequence, ?CLIENT_MASK}
- | ?SERVICE(?CLIENT)]).
+start_services(Config) ->
+ #group{client_service = CN,
+ client_strings = CD,
+ server_service = SN,
+ server_strings = SD}
+ = group(Config),
+ ok = diameter:start_service(SN, ?SERVICE(SN, SD)),
+ ok = diameter:start_service(CN, [{sequence, ?CLIENT_MASK}
+ | ?SERVICE(CN, CD)]).
add_transports(Config) ->
- LRef = ?util:listen(?SERVER,
+ #group{client_service = CN,
+ server_service = SN}
+ = group(Config),
+ LRef = ?util:listen(SN,
tcp,
[{capabilities_cb, fun capx/2},
{spawn_opt, [{min_heap_size, 8096}]},
{applications, apps(rfc3588)}]),
- Cs = [?util:connect(?CLIENT,
+ Cs = [?util:connect(CN,
tcp,
LRef,
[{id, Id},
@@ -354,12 +394,18 @@ outstanding(_Config) ->
is_atom(element(1,T))].
remove_transports(Config) ->
+ #group{client_service = CN,
+ server_service = SN}
+ = group(Config),
[LRef | Cs] = ?util:read_priv(Config, "transport"),
- [?util:disconnect(?CLIENT, C, ?SERVER, LRef) || C <- Cs].
+ [?util:disconnect(CN, C, SN, LRef) || C <- Cs].
-stop_services(_Config) ->
- ok = diameter:stop_service(?CLIENT),
- ok = diameter:stop_service(?SERVER).
+stop_services(Config) ->
+ #group{client_service = CN,
+ server_service = SN}
+ = group(Config),
+ ok = diameter:stop_service(CN),
+ ok = diameter:stop_service(SN).
%% Ensure even transports have been removed from request table.
empty(_Config) ->
@@ -439,8 +485,9 @@ send_arbitrary(Config) ->
['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
{'AVP', [#diameter_avp{name = 'Product-Name',
- value = "XXX"}]}
- = lists:last(Avps).
+ value = V}]}
+ = lists:last(Avps),
+ "XXX" = string(V, Config).
%% Send an unknown AVP (to some client) and check that it comes back.
send_unknown(Config) ->
@@ -594,9 +641,11 @@ send_nopeer(Config) ->
{error, no_connection} = call(Config, Req, [{extra, [?EXTRA]}]).
%% Send something on an unconfigured application.
-send_noapp(_Config) ->
+send_noapp(Config) ->
+ #group{client_service = CN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- {error, no_connection} = diameter:call(?CLIENT, unknown_alias, Req).
+ {error, no_connection} = diameter:call(CN, unknown_alias, Req).
%% Send something that's discarded by prepare_request.
send_discard(Config) ->
@@ -608,8 +657,10 @@ send_any_1(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
{error, no_connection} = call(Config, Req, [{filter, {any, []}}]).
send_any_2(Config) ->
+ #group{server_service = SN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}],
+ {'Destination-Host', [?HOST(SN, "unknown.org")]}],
?answer_message(?UNABLE_TO_DELIVER)
= call(Config, Req, [{filter, {any, [host, realm]}}]).
@@ -621,8 +672,10 @@ send_all_1(Config) ->
= call(Config, Req, [{filter, {all, [{host, any},
{realm, Realm}]}}]).
send_all_2(Config) ->
+ #group{server_service = SN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}],
+ {'Destination-Host', [?HOST(SN, "unknown.org")]}],
{error, no_connection}
= call(Config, Req, [{filter, {all, [host, realm]}}]).
@@ -655,8 +708,10 @@ send_encode_error(Config) ->
%% Send with filtering and expect success.
send_destination_1(Config) ->
+ #group{server_service = SN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(?SERVER, ?REALM)]}],
+ {'Destination-Host', [?HOST(SN, ?REALM)]}],
['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [host, realm]}}]).
send_destination_2(Config) ->
@@ -672,8 +727,10 @@ send_destination_3(Config) ->
{error, no_connection}
= call(Config, Req, [{filter, {all, [host, realm]}}]).
send_destination_4(Config) ->
+ #group{server_service = SN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}],
+ {'Destination-Host', [?HOST(SN, "unknown.org")]}],
{error, no_connection}
= call(Config, Req, [{filter, {all, [host, realm]}}]).
@@ -685,8 +742,10 @@ send_destination_5(Config) ->
?answer_message(?REALM_NOT_SERVED)
= call(Config, Req).
send_destination_6(Config) ->
+ #group{server_service = SN}
+ = group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}],
+ {'Destination-Host', [?HOST(SN, "unknown.org")]}],
?answer_message(?UNABLE_TO_DELIVER)
= call(Config, Req).
@@ -748,16 +807,31 @@ send_anything(Config) ->
%% ===========================================================================
+group(Config) ->
+ #group{} = proplists:get_value(group, Config).
+
+string(V, Config) ->
+ #group{client_strings = B} = group(Config),
+ decode(V,B).
+
+decode(S, true)
+ when is_list(S) ->
+ S;
+decode(B, false)
+ when is_binary(B) ->
+ binary_to_list(B).
+
call(Config, Req) ->
call(Config, Req, []).
call(Config, Req, Opts) ->
Name = proplists:get_value(testcase, Config),
- #group{client_encoding = ReqEncoding,
+ #group{client_service = CN,
+ client_encoding = ReqEncoding,
client_dict0 = Dict0}
= Group
- = proplists:get_value(group, Config),
- diameter:call(?CLIENT,
+ = group(Config),
+ diameter:call(CN,
dict(Req, Dict0),
msg(Req, ReqEncoding, Dict0),
[{extra, [{Name, Group}, diameter_lib:now()]} | Opts]).
@@ -844,35 +918,38 @@ peer_down(_SvcName, _Peer, State) ->
%% pick_peer/6-7
-pick_peer(Peers, _, ?CLIENT, _State, {Name, Group}, _)
+pick_peer(Peers, _, [$C|_], _State, {Name, Group}, _)
when Name /= send_detach ->
find(Group, Peers).
-pick_peer(_Peers, _, ?CLIENT, _State, {send_nopeer, _}, _, ?EXTRA) ->
+pick_peer(_Peers, _, [$C|_], _State, {send_nopeer, _}, _, ?EXTRA) ->
false;
-pick_peer(Peers, _, ?CLIENT, _State, {send_detach, Group}, _, {_,_}) ->
+pick_peer(Peers, _, [$C|_], _State, {send_detach, Group}, _, {_,_}) ->
find(Group, Peers).
-find(#group{server_encoding = A, server_container = C}, Peers) ->
+find(#group{client_service = CN,
+ server_encoding = A,
+ server_container = C},
+ Peers) ->
Id = {A,C},
- [P] = [P || P <- Peers, id(Id, P)],
+ [P] = [P || P <- Peers, id(Id, P, CN)],
{ok, P}.
-id(Id, {Pid, _Caps}) ->
+id(Id, {Pid, _Caps}, SvcName) ->
[{ref, _}, {type, _}, {options, Opts} | _]
- = diameter:service_info(?CLIENT, Pid),
+ = diameter:service_info(SvcName, Pid),
lists:member({id, Id}, Opts).
%% prepare_request/5-6
-prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, {send_discard, _}, _) ->
+prepare_request(_Pkt, [$C|_], {_Ref, _Caps}, {send_discard, _}, _) ->
{discard, unprepared};
-prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {Name, Group}, _) ->
+prepare_request(Pkt, [$C|_], {_Ref, Caps}, {Name, Group}, _) ->
{send, prepare(Pkt, Caps, Name, Group)}.
-prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {send_detach, Group}, _, _) ->
+prepare_request(Pkt, [$C|_], {_Ref, Caps}, {send_detach, Group}, _, _) ->
{eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}.
log(#diameter_packet{bin = Bin} = P, T)
@@ -1043,10 +1120,10 @@ prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) ->
%% handle_answer/6-7
-handle_answer(Pkt, Req, ?CLIENT, Peer, {Name, Group}, _) ->
+handle_answer(Pkt, Req, [$C|_], Peer, {Name, Group}, _) ->
answer(Pkt, Req, Peer, Name, Group).
-handle_answer(Pkt, Req, ?CLIENT, Peer, {send_detach = Name, Group}, _, X) ->
+handle_answer(Pkt, Req, [$C|_], Peer, {send_detach = Name, Group}, _, X) ->
{Pid, Ref} = X,
Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}.
@@ -1075,13 +1152,13 @@ app(Req, _, Dict0) ->
%% handle_error/6
-handle_error(timeout = Reason, _Req, ?CLIENT, _Peer, _, Time) ->
+handle_error(timeout = Reason, _Req, [$C|_], _Peer, _, Time) ->
Now = diameter_lib:now(),
{Reason, {diameter_lib:timestamp(Time),
diameter_lib:timestamp(Now),
diameter_lib:micro_diff(Now, Time)}};
-handle_error(Reason, _Req, ?CLIENT, _Peer, _, _Time) ->
+handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) ->
{error, Reason}.
%% handle_request/3
@@ -1089,7 +1166,7 @@ handle_error(Reason, _Req, ?CLIENT, _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}, ?SERVER, {_Ref, Caps}) ->
+handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
#diameter_header{end_to_end_id = EI,
hop_by_hop_id = HI}
= H,