summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErlang/OTP <otp@erlang.org>2020-02-27 16:13:17 +0100
committerErlang/OTP <otp@erlang.org>2020-02-27 16:13:17 +0100
commit9580f2b3f25a31c5d2bf86323816cb463bab1d3d (patch)
tree45eb869412396209e429333c265e815031a6f3a6
parent141b5f57d6ce5d1404c11722de9657a488b561d6 (diff)
parente3db347077f99b455ad671a9d768226ef2905ac0 (diff)
downloaderlang-9580f2b3f25a31c5d2bf86323816cb463bab1d3d.tar.gz
Merge branch 'anders/diameter/config/OTP-16459' into maint-22
* anders/diameter/config/OTP-16459: Skip traffic tests differently Skip many more traffic testcases Fix service/transport option inconsistencies Fix incorrect transport options Reorder service/transport options in type specs Add missing type spec Add missing module in dialyze target Fix comment typos/inaccuracies Don't orphan slave node in example suite Finish/fix redirect example Rework/simplify examples
-rw-r--r--lib/diameter/doc/src/diameter.xml5
-rw-r--r--lib/diameter/examples/code/GNUmakefile6
-rw-r--r--lib/diameter/examples/code/README105
-rw-r--r--lib/diameter/examples/code/client.erl171
-rw-r--r--lib/diameter/examples/code/client_cb.erl29
-rw-r--r--lib/diameter/examples/code/node.erl202
-rw-r--r--lib/diameter/examples/code/redirect.erl66
-rw-r--r--lib/diameter/examples/code/redirect_cb.erl62
-rw-r--r--lib/diameter/examples/code/relay.erl73
-rw-r--r--lib/diameter/examples/code/relay_cb.erl55
-rw-r--r--lib/diameter/examples/code/server.erl121
-rw-r--r--lib/diameter/examples/code/server_cb.erl101
-rw-r--r--lib/diameter/src/Makefile7
-rw-r--r--lib/diameter/src/base/diameter.erl42
-rw-r--r--lib/diameter/src/base/diameter_codec.erl4
-rw-r--r--lib/diameter/src/base/diameter_config.erl20
-rw-r--r--lib/diameter/src/base/diameter_service.erl83
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl14
-rw-r--r--lib/diameter/src/modules.mk5
-rw-r--r--lib/diameter/test/diameter_event_SUITE.erl47
-rw-r--r--lib/diameter/test/diameter_examples_SUITE.erl25
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl18
22 files changed, 712 insertions, 549 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 0ab6a122cd..aa4eb6ad45 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -1046,8 +1046,9 @@ each node from which requests are sent.</p>
<tag><c>&transport_opt;</c></tag>
<item>
<p>
-Any transport option except <c>applications</c> or
-<c>capabilities</c>.
+Any transport option except <c>applications</c>,
+<c>capabilities</c>, <c>transport_config</c>, and
+<c>transport_module</c>.
Used as defaults for transport configuration, values passed to
&add_transport; overriding values configured on the service.</p>
</item>
diff --git a/lib/diameter/examples/code/GNUmakefile b/lib/diameter/examples/code/GNUmakefile
index f5c2e5f869..9efb966e65 100644
--- a/lib/diameter/examples/code/GNUmakefile
+++ b/lib/diameter/examples/code/GNUmakefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2015. All Rights Reserved.
+# Copyright Ericsson AB 2010-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
# %CopyrightEnd%
#
-EXAMPLES = client server relay # redirect proxy
+EXAMPLES = client server relay redirect # proxy
CALLBACKS = $(EXAMPLES:%=%_cb)
-MODULES = node $(EXAMPLES) $(EXAMPLES:%=%_cb)
+MODULES = $(EXAMPLES) $(EXAMPLES:%=%_cb)
BEAM = $(MODULES:%=%.beam)
diff --git a/lib/diameter/examples/code/README b/lib/diameter/examples/code/README
new file mode 100644
index 0000000000..b639849390
--- /dev/null
+++ b/lib/diameter/examples/code/README
@@ -0,0 +1,105 @@
+
+This directory contains small examples of simple Diameter nodes. They
+don't do everything a real node should do obviously, but they're a
+starting point.
+
+Each example consists of an interface module with functions to start
+and stop a service and add transport, and a corresponding callback
+module for the Diameter application the service configures. A real
+node might support multiple Diameter applications, either with the
+same callback or sharing a common callback, maybe using extra
+arguments to distinguish between callbacks for the different
+applications.
+
+The interface functions are named start, stop, connect, and listen;
+the client example also has a call function that sends an example
+message. Service names should be atoms in these modules (since the
+default setting of Origin-Host assumes this), but doesn't need to be
+in general. Options are passed directly to diameter:start_service/2
+and diameter:add_transport/2, with some additional convenience options
+for the latter; in particular, the atoms tcp and sctp to connect to or
+listen on default endpoints (127.0.01:3868), or tuples with protocol
+and another endpoint {eg. {tcp, {192,168,1,5}, 3869}. This convenience
+makes the simplest usage like this in an Erlang shell:
+
+ diameter:start().
+ server:start().
+ server:listen(tcp).
+ client:start().
+ client:connect(tcp).
+ client:call().
+
+Or put a relay between the client and server:
+
+ diameter:start().
+ server:start().
+ server:listen(sctp).
+ relay:start().
+ relay:connect(sctp).
+ relay:listen(tcp).
+ client:start().
+ client:connect(tcp).
+ client:call().
+
+Most services should probably set the following options, which have
+been added to solve various problems over the years, while the
+defaults have not been changed for backwards compatibility.
+
+ {decode_format, map}
+
+ Provide decoded messages in #diameter_packet.msg of a
+ handle_request or handle_answer callback in the form [Name | Avps],
+ where Name is the atom() name of the message in the (Diameter)
+ application dictionary in question (eg. 'ACR') and Avps is a map
+ of AVP values. This avoids compile-time dependencies on the
+ generated records and their (generally) long-winded names. The
+ hrl files generated from dictionaries are best avoided.
+
+ {restrict_connections, false}
+
+ Accept multiple connections with the same peer. By default,
+ diameter will only accept a single connection with a given peer,
+ which the Diameter RFC can be interpreted as requiring. In
+ practice, wanting multiple connections to the same peer is
+ common.
+
+ {string_decode, false}
+
+ Disable the decoding of string-ish Diameter types to Erlang
+ strings, leaving them as binary(). Strings can be costly if
+ decoded Diameter messages are passed between processes.
+
+ {strict_mbit, false}
+
+ Relax the interpretation of the M-bit so that an AVP setting
+ this bit is not regarded as a 5001 error when the message
+ grammar in question doesn't explicitly list the AVP. Without
+ this, a Redirect-Host AVP received in a 3006
+ (DIAMETER_REDIRECT_INDICATION) answer-message from a redirect
+ agent will be treated as error, and there have been other
+ situations in which the default value has caused problems (or at
+ least surprise).
+
+ {call_mutates_state, false} (on each configured application)
+
+ Avoid pick_peer and subsequent callbacks going through a single
+ process, which can be a bottleneck. Better to use the tid() of
+ an ets table as (immutable) state, for example.
+
+Other options that are particularly useful/necessary in some
+situations are:
+
+ pool_size - Create a pool of accepting processes for a listening
+ transport, to avoid refused connections when many peers
+ connect simultaneously. Can also be used on a connecting
+ transport to establish multiple connections with a
+ single call to diameter:add_transport/2.
+
+ sequence - Ensure unique End-to-End and Hop-by-Hop identifiers over
+ a cluster of Erlang nodes.
+
+ share_peers - Share peer connections across a cluster of
+ use_shared_peers Erlang nodes.
+
+ spawn_opt - Replace diameter's spawning of a new handler process for
+ each request by something else: diameter_dist is an example.
diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl
index 0864919cdd..04c91e37fa 100644
--- a/lib/diameter/examples/code/client.erl
+++ b/lib/diameter/examples/code/client.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,120 +18,173 @@
%% %CopyrightEnd%
%%
+-module(client).
+
%%
-%% An example Diameter client that can sends base protocol RAR
+%% An example Diameter client that can sends base protocol ACR
%% requests to a connected peer.
%%
-%% The simplest usage is as follows this to connect to a server
-%% listening on the default port on the local host, assuming diameter
-%% is already started (eg. diameter:start()).
+%% Simplest usage to connect to a server listening on TCP at
+%% 127.0.0.1:3868:
%%
%% client:start().
%% client:connect(tcp).
%% client:call().
%%
-%% The first call starts the a service with the default name of
-%% ?MODULE, the second defines a connecting transport that results in
-%% a connection to the peer (if it's listening), the third sends it a
-%% RAR and returns the answer.
-%%
-
--module(client).
-
--include_lib("diameter/include/diameter.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
+ call/2, %% send a request
stop/1]). %% stop a service
-%% A real application would typically choose an encoding and whether
-%% they want the call to return the answer or not. Sending with
-%% both the record and list encoding here, one detached and one not,
-%% is just for demonstration purposes.
%% Convenience functions using the default service name.
-export([start/0,
connect/1,
stop/0,
call/0,
- cast/0]).
+ call/1]).
-define(DEF_SVC_NAME, ?MODULE).
-define(L, atom_to_list).
+-define(LOOPBACK, {127,0,0,1}).
-%% The service configuration. As in the server example, a client
-%% supporting multiple Diameter applications may or may not want to
-%% configure a common callback module on all applications.
+%% Service configuration.
-define(SERVICE(Name), [{'Origin-Host', ?L(Name) ++ ".example.com"},
{'Origin-Realm', "example.com"},
{'Vendor-Id', 0},
{'Product-Name', "Client"},
{'Auth-Application-Id', [0]},
- {string_decode, false},
{decode_format, map},
+ {restrict_connections, false},
+ {strict_mbit, false},
+ {string_decode, false},
{application, [{alias, common},
{dictionary, diameter_gen_base_rfc6733},
- {module, client_cb}]}]).
+ {module, client_cb},
+ {answer_errors, callback},
+ {call_mutates_state, false}]}]).
-%% start/1
+%% start/2
-start(Name)
- when is_atom(Name) ->
- start(Name, []);
+start(Name, Opts) ->
+ Defaults = [T || {K,_} = T <- ?SERVICE(Name),
+ not lists:keymember(K, 1, Opts)],
+ diameter:start_service(Name, Opts ++ Defaults).
-start(Opts)
- when is_list(Opts) ->
+%% start/1
+
+start(Opts) ->
start(?DEF_SVC_NAME, Opts).
%% start/0
start() ->
- start(?DEF_SVC_NAME).
+ start(?DEF_SVC_NAME, []).
-%% start/2
+%% connect/1
-start(Name, Opts) ->
- node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name),
- false == lists:keymember(K, 1, Opts)]).
+connect(Opts) ->
+ connect(?DEF_SVC_NAME, Opts).
%% connect/2
+connect(Name, Opts)
+ when is_list(Opts) ->
+ diameter:add_transport(Name, {connect, lists:flatmap(fun opts/1, Opts)});
+
+%% backwards compatibility with old config
+connect(Name, {T, Opts}) ->
+ connect(Name, [T | Opts]);
connect(Name, T) ->
- node:connect(Name, T).
+ connect(Name, [T]).
-connect(T) ->
- connect(?DEF_SVC_NAME, T).
+%% call/2
-%% call/1
+call(Name, #{'Session-Id' := _} = Avps) ->
+ Defaults = #{'Destination-Realm' => "example.com",
+ 'Accounting-Record-Type' => 1, %% EVENT_RECORD
+ 'Accounting-Record-Number' => 0},
+ ACR = ['ACR' | maps:merge(Defaults, Avps)],
+ diameter:call(Name, common, ACR, []);
-call(Name) ->
- SId = diameter:session_id(?L(Name)),
- RAR = ['RAR' | #{'Session-Id' => SId,
- 'Auth-Application-Id' => 0,
- 'Re-Auth-Request-Type' => 0}],
- diameter:call(Name, common, RAR, []).
+call(Name, #{} = Avps) ->
+ call(Name, Avps#{'Session-Id' => diameter:session_id(?L(Name))});
-call() ->
- call(?DEF_SVC_NAME).
+call(Name, Avps) ->
+ call(Name, maps:from_list(Avps)).
-%% cast/1
+%% call/1
+
+call(Avps) ->
+ call(?DEF_SVC_NAME, Avps).
-cast(Name) ->
- SId = diameter:session_id(?L(Name)),
- RAR = ['RAR', {'Session-Id', SId},
- {'Auth-Application-Id', 0},
- {'Re-Auth-Request-Type', 1}],
- diameter:call(Name, common, RAR, [detach]).
+%% call/0
-cast() ->
- cast(?DEF_SVC_NAME).
+call() ->
+ call(?DEF_SVC_NAME, #{}).
%% stop/1
stop(Name) ->
- node:stop(Name).
+ diameter:stop_service(Name).
stop() ->
stop(?DEF_SVC_NAME).
+
+%% ===========================================================================
+
+%% opts/1
+%%
+%% Map some terms to transport_module/transport_config pairs as a
+%% convenience, pass everything else unmodified.
+
+opts(T)
+ when T == any;
+ T == tcp;
+ T == sctp ->
+ opts({T, loopback, 3868});
+
+opts({T, RA, RP}) ->
+ opts({T, [], RA, RP});
+
+opts({T, loopback, RA, RP}) ->
+ opts({T, ?LOOPBACK, RA, RP});
+
+opts({T, LA, RA, RP})
+ when is_tuple(LA) ->
+ opts({T, [{ip, LA}], RA, RP});
+
+opts({any, Opts, RA, RP}) ->
+ All = Opts ++ opts(RA, RP),
+ [{transport_module, diameter_sctp},
+ {transport_config, All, 2000},
+ {transport_module, diameter_tcp},
+ {transport_config, All}];
+
+opts({tcp, Opts, RA, RP}) ->
+ opts({diameter_tcp, Opts, RA, RP});
+
+opts({sctp, Opts, RA, RP}) ->
+ opts({diameter_sctp, Opts, RA, RP});
+
+opts({Mod, Opts, loopback, RP}) ->
+ opts({Mod, Opts, ?LOOPBACK, RP});
+
+opts({Mod, Opts, RA, default}) ->
+ opts({Mod, Opts, RA, 3868});
+
+opts({Mod, Opts, RA, RP}) ->
+ [{transport_module, Mod},
+ {transport_config, opts(RA, RP) ++ Opts}];
+
+opts(T) ->
+ [T].
+
+%% opts/2
+
+opts(loopback, RP) ->
+ opts(?LOOPBACK, RP);
+
+opts(RA, RP) ->
+ [{raddr, RA}, {rport, RP}, {reuseaddr, true}].
diff --git a/lib/diameter/examples/code/client_cb.erl b/lib/diameter/examples/code/client_cb.erl
index af2d4d6da7..7721c47a9a 100644
--- a/lib/diameter/examples/code/client_cb.erl
+++ b/lib/diameter/examples/code/client_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
-module(client_cb).
-include_lib("diameter/include/diameter.hrl").
--include_lib("diameter/include/diameter_gen_base_rfc3588.hrl").
%% diameter callbacks
-export([peer_up/3,
@@ -50,28 +49,16 @@ pick_peer([Peer | _], _, _SvcName, _State) ->
%% prepare_request/3
-prepare_request(#diameter_packet{msg = ['RAR' = T | Avps]}, _, {_, Caps}) ->
- #diameter_caps{origin_host = {OH, DH},
- origin_realm = {OR, DR}}
+prepare_request(#diameter_packet{msg = [Name | Avps]}, _, {_, Caps}) ->
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}
= Caps,
-
- {send, [T | if is_map(Avps) ->
- Avps#{'Origin-Host' => OH,
- 'Origin-Realm' => OR,
- 'Destination-Host' => DH,
- 'Destination-Realm' => DR};
- is_list(Avps) ->
- [{'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Destination-Host', DH},
- {'Destination-Realm', DR}
- | Avps]
- end]}.
+ {send, [Name | Avps#{'Origin-Host' => OH, 'Origin-Realm' => OR}]}.
%% prepare_retransmit/3
-prepare_retransmit(Packet, SvcName, Peer) ->
- prepare_request(Packet, SvcName, Peer).
+prepare_retransmit(Pkt, _SvcName, _Peer) ->
+ {send, Pkt}.
%% handle_answer/4
@@ -86,4 +73,4 @@ handle_error(Reason, _Request, _SvcName, _Peer) ->
%% handle_request/3
handle_request(_Packet, _SvcName, _Peer) ->
- erlang:error({unexpected, ?MODULE, ?LINE}).
+ {answer_message, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
diff --git a/lib/diameter/examples/code/node.erl b/lib/diameter/examples/code/node.erl
deleted file mode 100644
index 77810bf893..0000000000
--- a/lib/diameter/examples/code/node.erl
+++ /dev/null
@@ -1,202 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% A library module used by the example Diameter nodes. Does little
-%% more than provide an alternate/simplified transport configuration.
-%%
-
--module(node).
-
--export([start/2,
- listen/2,
- connect/2,
- stop/1]).
-
--export([message/3]).
-
--type protocol()
- :: tcp | sctp.
-
--type ip_address()
- :: default
- | inet:ip_address().
-
--type server_transport()
- :: protocol()
- | {protocol(), ip_address(), non_neg_integer()}.
-
--type server_opts()
- :: server_transport()
- | {server_transport(), [diameter:transport_opt()]}
- | [diameter:transport_opt()].
-
--type client_transport()
- :: protocol() | any
- | {protocol() | any, ip_address(), non_neg_integer()}
- | {protocol() | any, ip_address(), ip_address(), non_neg_integer()}.
-
--type client_opts()
- :: client_transport()
- | {client_transport(), [diameter:transport_opt()]}
- | [diameter:transport_opt()].
-
-%% The server_transport() and client_transport() config is just
-%% convenience: arbitrary options can be specifed as a
-%% [diameter:transport_opt()].
-
--define(DEFAULT_PORT, 3868).
-
-%% ---------------------------------------------------------------------------
-%% Interface functions
-%% ---------------------------------------------------------------------------
-
-%% start/2
-
--spec start(diameter:service_name(), [diameter:service_opt()])
- -> ok
- | {error, term()}.
-
-start(Name, Opts)
- when is_atom(Name), is_list(Opts) ->
- diameter:start_service(Name, Opts).
-
-%% connect/2
-
--spec connect(diameter:service_name(), client_opts())
- -> {ok, diameter:transport_ref()}
- | {error, term()}.
-
-connect(Name, Opts)
- when is_list(Opts) ->
- diameter:add_transport(Name, {connect, Opts});
-
-connect(Name, {T, Opts}) ->
- connect(Name, Opts ++ client_opts(T));
-
-connect(Name, T) ->
- connect(Name, [{connect_timer, 5000} | client_opts(T)]).
-
-%% listen/2
-
--spec listen(diameter:service_name(), server_opts())
- -> {ok, diameter:transport_ref()}
- | {error, term()}.
-
-listen(Name, Opts)
- when is_list(Opts) ->
- diameter:add_transport(Name, {listen, Opts});
-
-listen(Name, {T, Opts}) ->
- listen(Name, Opts ++ server_opts(T));
-
-listen(Name, T) ->
- listen(Name, server_opts(T)).
-
-%% stop/1
-
--spec stop(diameter:service_name())
- -> ok
- | {error, term()}.
-
-stop(Name) ->
- diameter:stop_service(Name).
-
-%% ---------------------------------------------------------------------------
-%% Internal functions
-%% ---------------------------------------------------------------------------
-
-%% server_opts/1
-%%
-%% Return transport options for a listening transport.
-
-server_opts({T, Addr, Port}) ->
- [{transport_module, tmod(T)},
- {transport_config, [{reuseaddr, true},
- {sender, true},
- {message_cb, [fun ?MODULE:message/3, 0]},
- {ip, addr(Addr)},
- {port, Port}]}];
-
-server_opts(T) ->
- server_opts({T, loopback, ?DEFAULT_PORT}).
-
-%% client_opts/1
-%%
-%% Return transport options for a connecting transport.
-
-client_opts({T, LA, RA, RP})
- when T == all; %% backwards compatibility
- T == any ->
- [[S, {C,Os}], T] = [client_opts({P, LA, RA, RP}) || P <- [sctp,tcp]],
- [S, {C,Os,2000} | T];
-
-client_opts({T, LA, RA, RP}) ->
- [{transport_module, tmod(T)},
- {transport_config, [{raddr, addr(RA)},
- {rport, RP},
- {reuseaddr, true}
- | ip(LA)]}];
-
-client_opts({T, RA, RP}) ->
- client_opts({T, default, RA, RP});
-
-client_opts(T) ->
- client_opts({T, loopback, loopback, ?DEFAULT_PORT}).
-
-%% ---------------------------------------------------------------------------
-
-tmod(tcp) -> diameter_tcp;
-tmod(sctp) -> diameter_sctp.
-
-ip(default) ->
- [];
-ip(loopback) ->
- [{ip, {127,0,0,1}}];
-ip(Addr) ->
- [{ip, Addr}].
-
-addr(loopback) ->
- {127,0,0,1};
-addr(A) ->
- A.
-
-%% ---------------------------------------------------------------------------
-
-%% message/3
-%%
-%% Simple message callback that limits the number of concurrent
-%% requests on the peer connection in question.
-
-%% Incoming request.
-message(recv, <<_:32, 1:1, _/bits>> = Bin, N) ->
- [Bin, N < 32, fun ?MODULE:message/3, N+1];
-
-%% Outgoing request.
-message(ack, <<_:32, 1:1, _/bits>>, _) ->
- [];
-
-%% Incoming answer or request discarded.
-message(ack, _, N) ->
- [N =< 32, fun ?MODULE:message/3, N-1];
-
-%% Outgoing message or incoming answer.
-message(_, Bin, _) ->
- [Bin].
diff --git a/lib/diameter/examples/code/redirect.erl b/lib/diameter/examples/code/redirect.erl
index 6934e54507..1f9749ba84 100644
--- a/lib/diameter/examples/code/redirect.erl
+++ b/lib/diameter/examples/code/redirect.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,52 +20,80 @@
-module(redirect).
--include_lib("diameter/include/diameter.hrl").
--include_lib("diameter/include/diameter_gen_base_rfc3588.hrl").
+%%
+%% An example Diameter redirect agent.
+%%
+%% Simplest usage to listen on TCP at 127.0.0.1:3868.
+%%
+%% redirect:start().
+%% redirect:listen(tcp).
+%%
+%% Interface.
-export([start/1,
listen/2,
stop/1]).
+%% Convenience functions using the default service name.
-export([start/0,
listen/1,
stop/0]).
--define(APP_ALIAS, ?MODULE).
--define(SVC_NAME, ?MODULE).
--define(CALLBACK_MOD, redirect_mod).
+-define(DEF_SVC_NAME, ?MODULE).
%% The service configuration.
-define(SERVICE(Name), [{'Origin-Host', atom_to_list(Name) ++ ".example.com"},
{'Origin-Realm', "example.com"},
{'Vendor-Id', 193},
{'Product-Name', "RedirectAgent"},
- {'Auth-Application-Id', [?DIAMETER_APP_ID_RELAY]},
- {application, [{alias, ?APP_ALIAS},
- {dictionary, ?DIAMETER_DICT_RELAY},
- {module, ?CALLBACK_MOD}]}]).
+ {'Auth-Application-Id', [16#FFFFFFFF]},
+ {decode_format, map},
+ {restrict_connections, false},
+ {strict_mbit, false},
+ {string_decode, false},
+ {application, [{alias, redirect},
+ {dictionary, diameter_gen_relay},
+ {module, redirect_cb},
+ {call_mutates_state, false}]}]).
+
+%% start/0
+
+start() ->
+ start(?DEF_SVC_NAME).
%% start/1
start(Name)
when is_atom(Name) ->
- peer:start(Name, ?SERVICE(Name)).
+ start(Name, []);
-start() ->
- start(?SVC_NAME).
+start(Opts)
+ when is_list(Opts) ->
+ start(?DEF_SVC_NAME, Opts).
+
+%% start/2
+
+start(Name, Opts) ->
+ Defaults = [T || {K,_} = T <- ?SERVICE(Name),
+ not lists:keymember(K, 1, Opts)],
+ diameter:start_service(Name, Opts ++ Defaults).
%% listen/2
-listen(Name, T) ->
- peer:listen(Name, T).
+listen(Name, Opts) ->
+ server:listen(Name, Opts).
+
+%% listen/1
-listen(T) ->
- listen(?SVC_NAME, T).
+listen(Opts) ->
+ listen(?DEF_SVC_NAME, Opts).
%% stop/1
stop(Name) ->
- peer:stop(Name).
+ diameter:stop_service(Name).
+
+%% stop.0
stop() ->
- stop(?SVC_NAME).
+ stop(?DEF_SVC_NAME).
diff --git a/lib/diameter/examples/code/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl
index 8325e86391..76ab091be3 100644
--- a/lib/diameter/examples/code/redirect_cb.erl
+++ b/lib/diameter/examples/code/redirect_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
-module(redirect_cb).
-include_lib("diameter/include/diameter.hrl").
--include_lib("diameter/include/diameter_gen_base_rfc3588.hrl").
%% diameter callbacks
-export([peer_up/3,
@@ -33,30 +32,71 @@
handle_error/4,
handle_request/3]).
--define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
+%% Dictionary to encode answer-message AVPs with.
+-define(Dict, diameter_gen_base_rfc6733).
+
+%% Raise an error on callbacks that aren't expected.
+-define(ERROR, error({unexpected, ?MODULE, ?LINE})).
+
+%% peer_up/3
peer_up(_SvcName, _Peer, State) ->
State.
+%% peer_down/3
+
peer_down(_SvcName, _Peer, State) ->
State.
+%% pick_peer/4
+
pick_peer(_, _, _SvcName, _State) ->
- ?UNEXPECTED.
+ false.
+
+%% prepare_request/3
prepare_request(_, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% prepare_retransmit/3
prepare_retransmit(_Packet, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_answer/4
handle_answer(_Packet, _Request, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_error/4
handle_error(_Reason, _Request, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_request/3
-handle_request(#diameter_packet{msg = _, errors = []}, _SvcName, {_, Caps}) ->
- #diameter_caps{}
+handle_request(#diameter_packet{avps = Avps}, _, {_, Caps}) ->
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}
= Caps,
- discard. %% TODO
+
+ Tail = [#diameter_avp{data = {?Dict, A, V}}
+ || {A,V} <- [{'Origin-Host', OH},
+ {'Origin-Realm', OR},
+ {'Result-Code', 3006}, %% DIAMETER_REDIRECT_INDICATION
+ {'Redirect-Host', <<"aaa://server.example.com:3868">>}]],
+
+ {reply, ['answer-message' | lists:append([session(Avps), Tail])]}.
+
+%% ===========================================================================
+
+%% session/1
+
+session(Avps) ->
+ try
+ [] = [A || #diameter_avp{code = 263, vendor_id = undefined} = A
+ <- Avps,
+ throw(A)]
+ catch
+ Avp -> [Avp]
+ end.
diff --git a/lib/diameter/examples/code/relay.erl b/lib/diameter/examples/code/relay.erl
index 806f79915b..ec53ac01f1 100644
--- a/lib/diameter/examples/code/relay.erl
+++ b/lib/diameter/examples/code/relay.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,81 +18,94 @@
%% %CopyrightEnd%
%%
+-module(relay).
+
%%
%% An example Diameter relay agent.
%%
-%% Usage to connect to a server listening on the default port over TCP
-%% and to listen on the default port over SCTP is as follows, assuming
-%% diameter is already started (eg. diameter:start()).
+%% Simplets usage to connect to a server listening on TCP at
+%% 127.0.0.1:3868 and listen for connections on TCP at the same
+%% endpoint:
%%
-%% Eg. relay:start().
-%% relay:connect(tcp).
-%% relay:listen(sctp).
+%% relay:start().
+%% relay:connect(tcp).
+%% relay:listen(sctp).
%%
--module(relay).
-
+%% Interface.
-export([start/1,
start/2,
listen/2,
connect/2,
stop/1]).
+%% Convenience functions using the default service name.
-export([start/0,
listen/1,
connect/1,
stop/0]).
+%% Default service name.
-define(DEF_SVC_NAME, ?MODULE).
-%% The service configuration.
+%% Service configuration.
-define(SERVICE(Name), [{'Origin-Host', atom_to_list(Name) ++ ".example.com"},
{'Origin-Realm', "example.com"},
{'Vendor-Id', 193},
{'Product-Name', "RelayAgent"},
{'Auth-Application-Id', [16#FFFFFFFF]},
+ {decode_format, map},
+ {restrict_connections, false},
{string_decode, false},
+ {strict_mbit, false},
{application, [{alias, relay},
{dictionary, diameter_gen_relay},
- {module, relay_cb}]}]).
+ {module, relay_cb},
+ {call_mutates_state, false}]}]).
-%% start/1
+%% start/2
-start(Name)
- when is_atom(Name) ->
- start(Name, []).
+start(Name, Opts) ->
+ Defaults = [T || {K,_} = T <- ?SERVICE(Name),
+ not lists:keymember(K, 1, Opts)],
+ diameter:start_service(Name, Opts ++ Defaults).
%% start/1
-start() ->
- start(?DEF_SVC_NAME).
+start(Opts) ->
+ start(?DEF_SVC_NAME, Opts).
-%% start/2
+%% start/0
-start(Name, Opts) ->
- node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name),
- false == lists:keymember(K, 1, Opts)]).
+start() ->
+ start(?DEF_SVC_NAME, []).
%% listen/2
-listen(Name, T) ->
- node:listen(Name, T).
+listen(Name, Opts) ->
+ server:listen(Name, Opts).
-listen(T) ->
- listen(?DEF_SVC_NAME, T).
+%% listen/1
+
+listen(Opts) ->
+ listen(?DEF_SVC_NAME, Opts).
%% connect/2
-connect(Name, T) ->
- node:connect(Name, T).
+connect(Name, Opts) ->
+ client:connect(Name, Opts).
+
+%% connect/1
-connect(T) ->
- connect(?DEF_SVC_NAME, T).
+connect(Opts) ->
+ connect(?DEF_SVC_NAME, Opts).
%% stop/1
stop(Name) ->
- node:stop(Name).
+ diameter:stop_service(Name).
+
+%% stop/0
stop() ->
stop(?DEF_SVC_NAME).
diff --git a/lib/diameter/examples/code/relay_cb.erl b/lib/diameter/examples/code/relay_cb.erl
index 6df1738143..0d56a14401 100644
--- a/lib/diameter/examples/code/relay_cb.erl
+++ b/lib/diameter/examples/code/relay_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,48 +21,59 @@
-module(relay_cb).
-include_lib("diameter/include/diameter.hrl").
--include_lib("diameter/include/diameter_gen_base_rfc3588.hrl").
%% diameter callbacks
-export([peer_up/3,
peer_down/3,
- pick_peer/5,
- prepare_request/4,
- prepare_retransmit/4,
- handle_answer/5,
- handle_error/5,
+ pick_peer/4,
+ prepare_request/3,
+ prepare_retransmit/3,
+ handle_answer/4,
+ handle_error/4,
handle_request/3]).
+%% peer_up/3
+
peer_up(_SvcName, _Peer, State) ->
State.
+%% peer_down/3
+
peer_down(_SvcName, _Peer, State) ->
State.
-%% Returning 'relay' from handle_request causes diameter to resend the
-%% incoming request, which leads to pick_peer and prepare_request
-%% callbacks as if sending explicitly. The 'extra' argument is
-%% appended to the argument list for callbacks following from
-%% resending of the request.
+%% handle_request/3
+
+%% Assume the destination is directly connected; filter
+%% correspondingly; don't relay to the sender.
+handle_request(_Pkt, _SvcName, {_, Caps}) ->
+ #diameter_caps{origin_host = {_, OH}}
+ = Caps,
+ {relay, [{timeout, 2000},
+ {filter, {all, [host, realm, {neg, {host, OH}}]}}]}.
-handle_request(_Pkt, _SvcName, _Peer) ->
- {relay, [{timeout, 1000}, {extra, [relayed]}]}.
+%% pick_peer/4
-%% diameter will filter the sender in the Peers list.
-pick_peer([Peer | _], _, _SvcName, _State, relayed) ->
+pick_peer([Peer | _], _, _SvcName, _State) ->
{ok, Peer}.
-prepare_request(Pkt, _SvcName, _Peer, relayed) ->
+%% prepare_request/3
+
+prepare_request(Pkt, _SvcName, _Peer) ->
{send, Pkt}.
-prepare_retransmit(Pkt, _SvcName, _Peer, relayed) ->
+%% prepare_request/3
+
+prepare_retransmit(Pkt, _SvcName, _Peer) ->
{send, Pkt}.
-%% diameter expects handle_answer to return the diameter_packet record
-%% containing the answer when called for a relayed request.
+%% handle_answer/4
-handle_answer(Pkt, _Request, _SvcName, _Peer, relayed) ->
+%% Relay an answer by returning the first argument.
+handle_answer(Pkt, _Request, _SvcName, _Peer) ->
Pkt.
-handle_error(Reason, _Request, _SvcName, _Peer, relayed) ->
+%% handle_error/4
+
+handle_error(Reason, _Request, _SvcName, _Peer) ->
{error, Reason}.
diff --git a/lib/diameter/examples/code/server.erl b/lib/diameter/examples/code/server.erl
index a91be70664..6ee49fb678 100644
--- a/lib/diameter/examples/code/server.erl
+++ b/lib/diameter/examples/code/server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,23 +18,20 @@
%% %CopyrightEnd%
%%
+-module(server).
+
%%
-%% An example Diameter server that can respond to the base protocol
-%% RAR sent by the client example.
+%% An example Diameter server that answers the base protocol ACR sent
+%% by the client example.
%%
-%% The simplest example to start a server listening on the loopback
-%% address (which will serve the example usage given in client.erl) is
-%% like this assuming diameter is already started (eg. diameter:start()):
+%% Simplest usage to listen on TCP at 127.0.0.1:3868:
%%
%% server:start().
%% server:listen(tcp).
%%
-%% The first call starts a service, the second adds a transport listening
-%% on the default port.
-%%
--module(server).
+%% Interface.
-export([start/1, %% start a service
start/2, %%
listen/2, %% add a listening transport
@@ -45,6 +42,9 @@
listen/1,
stop/0]).
+%% Internal callback.
+-export([message/3]).
+
-define(DEF_SVC_NAME, ?MODULE).
%% The service configuration. In a server supporting multiple Diameter
@@ -55,45 +55,110 @@
{'Vendor-Id', 193},
{'Product-Name', "Server"},
{'Auth-Application-Id', [0]},
+ {decode_format, map},
{restrict_connections, false},
+ {strict_mbit, false},
{string_decode, false},
{application, [{alias, common},
{dictionary, diameter_gen_base_rfc6733},
- {module, server_cb}]}]).
+ {module, server_cb},
+ {call_mutates_state, false}]}]).
-%% start/1
+%% start/2
-start(Name)
- when is_atom(Name) ->
- start(Name, []);
+start(Name, Opts) ->
+ Defaults = [T || {K,_} = T <- ?SERVICE(Name),
+ not lists:keymember(K, 1, Opts)],
+ diameter:start_service(Name, Opts ++ Defaults).
-start(Opts)
- when is_list(Opts) ->
+%% start/1
+
+start(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)]).
+ start(?DEF_SVC_NAME, []).
%% listen/2
+listen(Name, Opts)
+ when is_list(Opts) ->
+ diameter:add_transport(Name, {listen, lists:flatmap(fun opts/1, Opts)});
+
+%% backwards compatibility with old config
+listen(Name, {T, Opts}) ->
+ listen(Name, [T | Opts]);
listen(Name, T) ->
- node:listen(Name, T).
+ listen(Name, [T]).
-listen(T) ->
- listen(?DEF_SVC_NAME, T).
+%% listen/1
+
+listen(Opts) ->
+ listen(?DEF_SVC_NAME, Opts).
%% stop/1
stop(Name) ->
- node:stop(Name).
+ diameter:stop_service(Name).
+
+%% stop/0
stop() ->
stop(?DEF_SVC_NAME).
+
+%% ===========================================================================
+
+%% opts/1
+%%
+%% Map a 3-tuple a transport_module/transport_config pair as a
+%% convenience, pass everything else unmodified.
+
+opts(T)
+ when T == tcp;
+ T == sctp ->
+ opts({T, loopback, default});
+
+opts({tcp, Addr, Port}) ->
+ opts({diameter_tcp, Addr, Port});
+
+opts({sctp, Addr, Port}) ->
+ opts({diameter_sctp, Addr, Port});
+
+opts({Mod, loopback, Port}) ->
+ opts({Mod, {127,0,0,1}, Port});
+
+opts({Mod, Addr, default}) ->
+ opts({Mod, Addr, 3868});
+
+opts({Mod, Addr, Port}) ->
+ [{transport_module, Mod},
+ {transport_config, [{reuseaddr, true},
+ {sender, true},
+ {message_cb, {?MODULE, message, [0]}},
+ {ip, Addr},
+ {port, Port}]}];
+opts(T) ->
+ [T].
+
+%% message/3
+%%
+%% Simple message callback that limits the number of concurrent
+%% requests on the peer connection in question.
+
+%% Incoming request.
+message(recv, <<_:32, 1:1, _/bits>> = Bin, N) ->
+ [Bin, N < 32, {?MODULE, message, [N+1]}];
+
+%% Outgoing request.
+message(ack, <<_:32, 1:1, _/bits>>, _) ->
+ [];
+
+%% Incoming answer or request discarded.
+message(ack, _, N) ->
+ [N =< 32, {?MODULE, message, [N-1]}];
+
+%% Outgoing message or incoming answer.
+message(_, Bin, _) ->
+ [Bin].
diff --git a/lib/diameter/examples/code/server_cb.erl b/lib/diameter/examples/code/server_cb.erl
index a2fb8fbda6..5662717c00 100644
--- a/lib/diameter/examples/code/server_cb.erl
+++ b/lib/diameter/examples/code/server_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
-module(server_cb).
-include_lib("diameter/include/diameter.hrl").
--include_lib("diameter/include/diameter_gen_base_rfc6733.hrl").
%% diameter callbacks
-export([peer_up/3,
@@ -37,67 +36,87 @@
handle_error/4,
handle_request/3]).
--define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
+%% Raise an error on callbacks that aren't expected.
+-define(ERROR, error({unexpected, ?MODULE, ?LINE})).
+
+%% peer_up/3
peer_up(_SvcName, _Peer, State) ->
State.
+%% peer_down/3
+
peer_down(_SvcName, _Peer, State) ->
State.
-pick_peer(_, _, _SvcName, _State) ->
- ?UNEXPECTED.
+%% pick_peer/3
+
+%% Don't let requests be sent, so other request callbacks shouldn't
+%% happen.
+pick_peer(_LocalCandidates, _RemoteCandidates, _SvcName, _State) ->
+ false.
+
+%% prepare_request/3
-prepare_request(_, _SvcName, _Peer) ->
- ?UNEXPECTED.
+prepare_request(_Packet, _SvcName, _Peer) ->
+ ?ERROR.
+
+%% prepare_retransmit/3
prepare_retransmit(_Packet, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_answer/4
handle_answer(_Packet, _Request, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_error/4
handle_error(_Reason, _Request, _SvcName, _Peer) ->
- ?UNEXPECTED.
+ ?ERROR.
+
+%% handle_request/3
-%% A request whose decode was successful ...
-handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps})
- when is_record(Req, diameter_base_RAR) ->
+%% ACR without decode errors.
+handle_request(#diameter_packet{msg = ['ACR' | #{} = Request],
+ errors = []},
+ _SvcName,
+ {_, Caps}) ->
#diameter_caps{origin_host = {OH,_},
origin_realm = {OR,_}}
= Caps,
- #diameter_base_RAR{'Session-Id' = Id,
- 'Re-Auth-Request-Type' = Type}
- = Req,
-
- {reply, #diameter_base_RAA{'Result-Code' = rc(Type),
- 'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id}};
-
-%% ... or one that wasn't. 3xxx errors are answered by diameter itself
-%% but these are 5xxx errors for which we must contruct a reply.
-%% diameter will set Result-Code and Failed-AVP's.
-handle_request(#diameter_packet{msg = Req}, _SvcName, {_, Caps})
- when is_record(Req, diameter_base_RAR) ->
+
+ #{'Session-Id' := Sid,
+ 'Accounting-Record-Type' := T,
+ 'Accounting-Record-Number' := N}
+ = Request,
+
+ Answer = #{'Result-Code' => 2001, %% DIAMETER_SUCCESS
+ 'Origin-Host' => OH,
+ 'Origin-Realm' => OR,
+ 'Session-Id' => Sid,
+ 'Accounting-Record-Type' => T,
+ 'Accounting-Record-Number' => N},
+
+ {reply, ['ACA' | Answer]};
+
+%% ACR with decode errors.
+handle_request(#diameter_packet{msg = ['ACR' | #{} = Request]},
+ _SvcName,
+ {_, Caps}) ->
#diameter_caps{origin_host = {OH,_},
origin_realm = {OR,_}}
= Caps,
- #diameter_base_RAR{'Session-Id' = Id}
- = Req,
- {reply, #diameter_base_RAA{'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id}};
+ Answer = maps:merge(maps:with(['Session-Id'], Request),
+ #{'Origin-Host' => OH,
+ 'Origin-Realm' => OR}),
-%% Answer that any other message is unsupported.
+ %% Let diameter set Result-Code and Failed-AVP if there were
+ %% decode errors.
+ {reply, ['answer-message' | Answer]};
+
+%% Answer anything else as unsupported.
handle_request(#diameter_packet{}, _SvcName, _) ->
{answer_message, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
-
-%% Map Re-Auth-Request-Type to Result-Code just for the purpose of
-%% generating different answers.
-
-rc(0) ->
- 2001; %% DIAMETER_SUCCESS
-rc(_) ->
- 5012. %% DIAMETER_UNABLE_TO_COMPLY
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index 2d6dcc811e..d6854cfd27 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -228,9 +228,12 @@ dialyze: opt $(PLT)
-Wno_improper_lists \
$(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR) \
$(patsubst %, $(EBIN)/%.$(EMULATOR), \
- $(notdir $(RT_MODULES) $(CT_MODULES) $(INFO_MODULES)))
+ $(notdir $(DICT_YRL) \
+ $(RT_MODULES) \
+ $(CT_MODULES) \
+ $(INFO_MODULES)))
# Omit all but the common dictionary module since these
-# (diameter_gen_relay in particular) generate warning depending on how
+# (diameter_gen_relay in particular) generate warnings depending on how
# much of the included diameter_gen.hrl they use.
# ----------------------------------------------------
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index ee5ff38a42..2982486a10 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -352,45 +352,45 @@ call(SvcName, App, Message) ->
%% Options common to both start_service/2 and add_transport/2.
-type common_opt()
- :: {pool_size, pos_integer()}
+ :: {avp_dictionaries, [module()]}
| {capabilities_cb, eval()}
| {capx_timeout, 'Unsigned32'()}
- | {strict_capx, boolean()}
- | {strict_mbit, boolean()}
- | {avp_dictionaries, [module()]}
+ | {connect_timer, 'Unsigned32'()}
| {disconnect_cb, eval()}
- | {dpr_timeout, 'Unsigned32'()}
| {dpa_timeout, 'Unsigned32'()}
+ | {dpr_timeout, 'Unsigned32'()}
| {incoming_maxlen, message_length()}
| {length_errors, exit | handle | discard}
- | {connect_timer, 'Unsigned32'()}
- | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
+ | {pool_size, pos_integer()}
+ | {spawn_opt, list() | mfa()}
+ | {strict_capx, boolean()}
+ | {strict_mbit, boolean()}
| {watchdog_config, [{okay|suspect, non_neg_integer()}]}
- | {spawn_opt, list() | mfa()}.
+ | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}.
%% Options passed to start_service/2
-type service_opt()
:: capability()
| {application, [application_opt()]}
+ | {decode_format, decode_format()}
| {restrict_connections, restriction()}
| {sequence, sequence() | eval()}
| {share_peers, remotes()}
- | {decode_format, decode_format()}
- | {traffic_counters, boolean()}
- | {string_decode, boolean()}
| {strict_arities, true | strict_arities()}
+ | {string_decode, boolean()}
+ | {traffic_counters, boolean()}
| {use_shared_peers, remotes()}
| common_opt().
-type application_opt()
:: {alias, app_alias()}
+ | {answer_errors, callback|report|discard}
+ | {call_mutates_state, boolean()}
| {dictionary, module()}
| {module, app_module()}
- | {state, any()}
- | {call_mutates_state, boolean()}
- | {answer_errors, callback|report|discard}
- | {request_errors, answer_3xxx|answer|callback}.
+ | {request_errors, answer_3xxx|answer|callback}
+ | {state, any()}.
-type app_alias()
:: any().
@@ -408,11 +408,11 @@ call(SvcName, App, Message) ->
%% Options passed to add_transport/2
-type transport_opt()
- :: {transport_module, atom()}
+ :: {applications, [app_alias()]}
+ | {capabilities, [capability()]}
| {transport_config, any()}
| {transport_config, any(), 'Unsigned32'() | infinity}
- | {applications, [app_alias()]}
- | {capabilities, [capability()]}
+ | {transport_module, atom()}
| common_opt()
| {private, any()}.
@@ -431,8 +431,8 @@ call(SvcName, App, Message) ->
%% Options passed to call/4
-type call_opt()
- :: {extra, list()}
+ :: detach
+ | {extra, list()}
| {filter, peer_filter()}
- | {timeout, 'Unsigned32'()}
| {peer, peer_ref()}
- | detach.
+ | {timeout, 'Unsigned32'()}.
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 493a6ab1e3..7f6baf666a 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -208,7 +208,7 @@ values(Avps) ->
encode_avps(_, _, [#diameter_avp{} | _] = Avps, Opts) ->
encode_avps(Avps, Opts);
-%% ... or as a tuple list or record.
+%% ... or as a tuple list, map, or record.
encode_avps(Mod, MsgName, Values, Opts) ->
Mod:encode_avps(MsgName, Values, Opts).
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 36ae4c2276..495e57e456 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -661,6 +661,9 @@ opt(transport, {transport_module, M}) ->
opt(transport, {transport_config, _, Tmo}) ->
?IS_UINT32(Tmo) orelse Tmo == infinity;
+opt(transport, {transport_config, _}) ->
+ true;
+
opt(transport, {applications, As}) ->
is_list(As);
@@ -720,15 +723,16 @@ opt(_, {K, _})
when K == disconnect_cb;
K == capabilities_cb ->
true;
-opt(transport, {K, _})
- when K == transport_config;
- K == private ->
+opt(transport, {private, _}) ->
true;
-%% Anything else, which is ignored in transport config. This makes
-%% options sensitive to spelling mistakes, but arbitrary options are
-%% passed by some users as a way to identify transports so can't just
-%% do away with it.
+%% Anything else is ignored in transport config. This makes options
+%% sensitive to spelling mistakes and unintentionally passing service
+%% options, but arbitrary options are passed by some users as a way to
+%% identify transports (they can be returned by diameter:service_info/2
+%% for example) so can't just do away with it, although silently
+%% swallowing service options is at least debatable. The documentation
+%% says anything, so accept anything.
opt(K, _) ->
K == transport.
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 77d184cfc7..520a7233cc 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -735,29 +735,31 @@ init_peers() ->
%% Alias,
%% TPid}
+%% Valid service options are all 2-tuples.
service_opts(Opts) ->
- remove([{strict_arities, true},
- {avp_dictionaries, []}],
- maps:merge(maps:from_list([{monitor, false} | def_opts()]),
- maps:from_list(Opts))).
+ remove([{strict_arities, true}, {avp_dictionaries, []}],
+ merge(lists:append([[{monitor, false}] | def_opts()]), Opts)).
+
+merge(List1, List2) ->
+ maps:merge(maps:from_list(List1), maps:from_list(List2)).
remove(List, Map) ->
maps:filter(fun(K,V) -> not lists:member({K,V}, List) end,
Map).
-def_opts() -> %% defaults on the service map
- [{share_peers, false},
- {use_shared_peers, false},
- {sequence, {0,32}},
- {restrict_connections, nodes},
- {incoming_maxlen, 16#FFFFFF},
- {strict_arities, true},
- {strict_mbit, true},
- {decode_format, record},
- {avp_dictionaries, []},
- {traffic_counters, true},
- {string_decode, true},
- {spawn_opt, []}].
+def_opts() -> %% defaults on the options map
+ [[{decode_format, record}, %% service options
+ {restrict_connections, nodes},
+ {sequence, {0,32}},
+ {share_peers, false},
+ {strict_arities, true},
+ {string_decode, true},
+ {traffic_counters, true},
+ {use_shared_peers, false}],
+ [{avp_dictionaries, []}, %% common options
+ {incoming_maxlen, 16#FFFFFF},
+ {spawn_opt, []},
+ {strict_mbit, true}]].
mref(false = No) ->
No;
@@ -875,27 +877,46 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
= Svc1
= merge_service(Opts, Svc0),
Svc = binary_caps(Svc1, SD),
- {SOpts, TOpts} = merge_opts(SvcOpts, Opts),
- RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, SOpts]),
- T = {TOpts, SOpts, RecvData, Svc},
+ {Map, Rest} = merge_opts(SvcOpts, Opts),
+ RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, Map]),
+ T = {Rest, Map, RecvData, Svc},
Rec = #watchdog{type = Type,
ref = Ref,
- options = TOpts},
-
+ options = Opts}, %% original options, returned
+ %% by service_info/2
diameter_lib:fold_n(fun(_,A) ->
[wd(Type, Ref, T, WatchdogT, Rec) | A]
end,
[],
N).
-merge_opts(SvcOpts, Opts) ->
- Keys = [K || {K,_} <- def_opts()],
- SO = [T || {K,_} = T <- Opts, lists:member(K, Keys)],
- TO = Opts -- SO,
- {maps:merge(maps:with(Keys, SvcOpts), maps:from_list(SO)),
- TO ++ [T || {K,_} = T <- maps:to_list(SvcOpts),
- not lists:member(K, Keys),
- not lists:keymember(K, 1, Opts)]}.
+%% This is awkward. We have service options that have been passed to
+%% diameter:start_service/2 (minus application and capabilities
+%% options, removed in diameter_config) and transport options passed
+%% to diameter:add_transport/2. The former can include defaults for
+%% the latter, but the latter can also contain arbitrary options that
+%% are just returned by diameter:service_info/2. There's nothing
+%% stopping these arbitrary options from being valid service options,
+%% but these aren't interpreted as such.
+%%
+%% The options are merged (transport defaults have already been merged
+%% into the service options in service_opts/1) and split into a map
+%% for the service options and a few transport options, and a list for
+%% the rest. This is historical convolution. Some options are are
+%% pulled out of the list on the way to starting the transport process
+%% in diameter_peer_fsm, but more work could probably be done here to
+%% simplify things.
+%%
+%% Transport options are not necessarily 2-tuples: the transport_config
+%% 3-tuple means they can't just be turned into a map.
+merge_opts(SOpts, TOpts) ->
+ [SD,TD] = Def = def_opts(),
+ Keys = [K || L <- Def, {K,_} <- L],
+ Opts = [T || {K,_} = T <- TOpts, lists:keymember(K, 1, TD)],
+ {maps:merge(maps:with(Keys, SOpts), maps:from_list(Opts)),%% merge TOpts
+ TOpts ++ [T || {K,_} = T <- maps:to_list(SOpts), %% append SOpts
+ not lists:keymember(K, 1, SD),
+ [] == [A || {A,_} <- TOpts, A == K]]}.
binary_caps(Svc, true) ->
Svc;
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 76f2420c7b..4667bbc3f2 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -86,6 +86,7 @@
string_decode := boolean(),
strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
+ ordered_encode => 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
@@ -526,7 +527,7 @@ request_cb(noreply, _App, EvalPktFs, EvalFs) ->
%% Relay a request to another peer. This is equivalent to doing an
%% explicit call/4 with the message in question except that (1) a loop
-%% will be detected by examining Route-Record AVP's, (3) a
+%% will be detected by examining Route-Record AVP's, (2) a
%% Route-Record AVP will be added to the outgoing request and (3) the
%% End-to-End Identifier will default to that in the
%% #diameter_header{} without the need for an end_to_end_identifier
@@ -562,14 +563,7 @@ request_cb(T, App, _, _) ->
send_A({reply, Ans}, TPid, App, Dict0, RecvData, Pkt, _Caps, Fs) ->
AppDict = App#diameter_app.dictionary,
MsgDict = msg_dict(AppDict, Dict0, Ans),
- send_answer(Ans,
- TPid,
- MsgDict,
- AppDict,
- Dict0,
- RecvData,
- Pkt,
- Fs);
+ send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs);
send_A({call, Opts}, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) ->
AppDict = App#diameter_app.dictionary,
@@ -673,7 +667,7 @@ is_answer_message(#diameter_packet{msg = Msg}, Dict0) ->
is_answer_message([#diameter_header{is_request = R, is_error = E} | _], _) ->
E andalso not R;
-%% Message sent as a map or tagged avp/value list.
+%% Message sent as a map or avp list.
is_answer_message([Name | _], _) ->
Name == 'answer-message';
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index d16292bb88..cf938785e3 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2019. All Rights Reserved.
+# Copyright Ericsson AB 2010-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -98,13 +98,14 @@ BINS = \
# Released files relative to ../examples.
EXAMPLES = \
code/GNUmakefile \
- code/node.erl \
code/client.erl \
code/client_cb.erl \
code/server.erl \
code/server_cb.erl \
code/relay.erl \
code/relay_cb.erl \
+ code/redirect.erl \
+ code/redirect_cb.erl \
dict/rfc4004_mip.dia \
dict/rfc4005_nas.dia \
dict/rfc4006_cc.dia \
diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl
index a291dde6be..6d1d1dfd8f 100644
--- a/lib/diameter/test/diameter_event_SUITE.erl
+++ b/lib/diameter/test/diameter_event_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
-export([suite/0,
all/0,
+ init_per_suite/1,
+ end_per_suite/1,
init_per_testcase/2,
end_per_testcase/2]).
@@ -85,6 +87,13 @@ all() ->
cea_timeout,
stop].
+%% Not used, but a convenient place to enable trace.
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
init_per_testcase(Name, Config) ->
[{name, Name} | Config].
@@ -109,17 +118,19 @@ start_server(Config) ->
%% Connect with matching capabilities and expect the connection to
%% come up.
up(Config) ->
- {Svc, Ref} = connect(Config, [{connect_timer, 5000},
- {watchdog_timer, 15000}]),
+ {Svc, Ref, T} = connect(Config, [{strict_mbit, false},
+ {connect_timer, 5000},
+ {watchdog_timer, 15000}]),
start = event(Svc),
- {up, Ref, {TPid, Caps}, Cfg, #diameter_packet{msg = M}} = event(Svc),
+ {{up, Ref, {TPid, Caps}, T, #diameter_packet{msg = M}}, _}
+ = {event(Svc), T},
['CEA' | #{}] = M, %% assert
{watchdog, Ref, _, {initial, okay}, _} = event(Svc),
%% Kill the transport process and see that the connection is
%% reestablished after a watchdog timeout, not after connect_timer
%% expiry.
exit(TPid, kill),
- {down, Ref, {TPid, Caps}, Cfg} = event(Svc),
+ {{down, Ref, {TPid, Caps}, T}, _} = {event(Svc), T},
{watchdog, Ref, _, {okay, down}, _} = event(Svc),
{reconnect, Ref, _} = event(Svc, 10000, 20000).
@@ -127,24 +138,25 @@ up(Config) ->
%% to indicate as much and then for the transport to be restarted
%% (after connect_timer).
down(Config) ->
- {Svc, Ref} = connect(Config, [{capabilities, [{'Acct-Application-Id',
- [?DICT_ACCT:id()]}]},
- {applications, [?DICT_ACCT]},
- {connect_timer, 5000},
- {watchdog_timer, 20000}]),
+ {Svc, Ref, T} = connect(Config, [{capabilities, [{'Acct-Application-Id',
+ [?DICT_ACCT:id()]}]},
+ {applications, [?DICT_ACCT]},
+ {connect_timer, 5000},
+ {watchdog_timer, 20000}]),
start = event(Svc),
- {closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{msg = M}}, _}
- = event(Svc),
+ {{closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{msg = M}}, T},
+ _}
+ = {event(Svc), T},
['CEA' | #{}] = M, %% assert
{reconnect, Ref, _} = event(Svc, 4000, 10000).
%% Connect with matching capabilities but have the server delay its
%% CEA and cause the client to timeout.
cea_timeout(Config) ->
- {Svc, Ref} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2},
- {connect_timer, 2*?SERVER_CAPX_TMO}]),
+ {Svc, Ref, T} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2},
+ {connect_timer, 2*?SERVER_CAPX_TMO}]),
start = event(Svc),
- {closed, Ref, {'CEA', timeout}, _} = event(Svc).
+ {{closed, Ref, {'CEA', timeout}, T}, _} = {event(Svc), T}.
stop(_Config) ->
ok = diameter:stop().
@@ -168,8 +180,9 @@ connect(Config, Opts) ->
Name = Pre ++ uniq() ++ ?CLIENT,
diameter:subscribe(Name),
ok = start_service(Name, ?SERVICE(Name, [?DICT_COMMON, ?DICT_ACCT])),
- {ok, Ref} = diameter:add_transport(Name, opts(Config, Opts)),
- {Name, Ref}.
+ {connect, _} = T = opts(Config, Opts),
+ {ok, Ref} = diameter:add_transport(Name, T),
+ {Name, Ref, T}.
uniq() ->
"-" ++ diameter_util:unique_string().
diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl
index ee44ed8dc9..7d47efd527 100644
--- a/lib/diameter/test/diameter_examples_SUITE.erl
+++ b/lib/diameter/test/diameter_examples_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@
-export([suite/0,
all/0,
groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
init_per_group/2,
end_per_group/2]).
@@ -85,6 +87,13 @@ groups() ->
[{all, [parallel], [{group, P} || P <- ?PROTS]}
| [{P, [], Tc} || P <- ?PROTS]].
+%% Not used, but a convenient place to enable trace.
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
init_per_group(all, Config) ->
Config;
@@ -216,12 +225,14 @@ make_name(Dict) ->
%% Compile example code under examples/code.
code(Config) ->
- Node = slave(compile, here()),
- [] = rpc:call(Node,
- ?MODULE,
- install,
- [proplists:get_value(priv_dir, Config)]),
- {ok, Node} = ct_slave:stop(compile).
+ try
+ [] = rpc:call(slave(compile, here()),
+ ?MODULE,
+ install,
+ [proplists:get_value(priv_dir, Config)])
+ after
+ {ok, _} = ct_slave:stop(compile)
+ end.
%% Compile on another node since the code path may be modified.
install(PrivDir) ->
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 47b00c25a2..452bd28333 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -128,7 +128,7 @@
%% ===========================================================================
%% Fraction of shuffle/parallel groups to randomly skip.
--define(SKIP, 0.25).
+-define(SKIP, 0.90).
%% Positive number of testcases from which to select (randomly) from
%% tc(), the list of testcases to run, or [] to run all. The random
@@ -305,7 +305,8 @@ names() ->
S <- ?STRING_DECODES,
ST <- ?CALLBACKS,
SS <- ?SENDERS,
- CS <- ?SENDERS].
+ CS <- ?SENDERS,
+ ?SKIP =< rand:uniform()].
names(Names, []) ->
[N || N <- Names,
@@ -336,14 +337,9 @@ init_per_group(_) ->
init_per_group(Name, Config)
when Name == shuffle;
Name == parallel ->
- case rand:uniform() < ?SKIP of
- true ->
- {skip, random};
- false ->
- start_services(Config),
- add_transports(Config),
- replace({sleep, Name == parallel}, Config)
- end;
+ start_services(Config),
+ add_transports(Config),
+ replace({sleep, Name == parallel}, Config);
init_per_group(sctp = Name, Config) ->
{_, Sctp} = lists:keyfind(Name, 1, Config),