diff options
27 files changed, 837 insertions, 592 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 85522c99b2..aa4eb6ad45 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -23,7 +23,7 @@ <copyright> <year>2011</year> -<year>2019</year> +<year>2020</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -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> @@ -1398,10 +1399,11 @@ Options <c>monitor</c> and <c>link</c> are ignored in the list-valued case. An MFA is applied with an additional term prepended to its argument list, and should return either the pid of the handler process that -invokes <c>diameter_traffic:request/1</c> on the term in order to +invokes <c>diameter_traffic:request/1</c> on the argument in order to process the request, or the atom <c>discard</c>. -The handler process need not be local, but diameter must be started on -the remote node.</p> +The handler process need not be local, and diameter need not be +started on the remote node, but diameter and relevant application +callbacks must be on the code path.</p> <p> Defaults to the empty list.</p> 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 98636ed6e2..d6854cfd27 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -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. @@ -210,12 +210,16 @@ realclean: clean PLT = ./otp.plt -plt: +plt: $(PLT) + +$(PLT): dialyzer --build_plt \ --apps erts stdlib kernel \ xmerl ssl public_key crypto \ compiler syntax_tools runtime_tools \ - --output_plt $(PLT) \ + --output_plt $@ \ + --get_warnings \ + --statistics \ --verbose dialyze: opt $(PLT) @@ -224,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 7f172e1fa1..2982486a10 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.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. @@ -146,8 +146,9 @@ services() -> %% service_info/2 %% --------------------------------------------------------------------------- --spec service_info(service_name(), atom() | [atom()]) - -> any(). +-spec service_info(service_name(), Item | [Item]) + -> any() + when Item :: atom() | peer_ref(). service_info(SvcName, Option) -> diameter_service:info(SvcName, Option). @@ -351,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(). @@ -407,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()}. @@ -430,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_dist.erl b/lib/diameter/src/base/diameter_dist.erl index ed23152b8b..1edca58eb9 100644 --- a/lib/diameter/src/base/diameter_dist.erl +++ b/lib/diameter/src/base/diameter_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2019. All Rights Reserved. +%% Copyright Ericsson AB 2019-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,21 @@ %% transport configuration, to be able to distribute incoming Diameter %% requests to handler processes (local or remote) in various ways. %% +%% The gen_server implemented here must be started on each node on +%% which {diameter_dist, route_session, _} is configured as a +%% spawn_opt MFA, as well as each node that wants to receive requests. +%% This happens as a consequence of diameter application start, but a +%% minimal solution could start only this server on nodes that should +%% handle requests but not configure transport. (Although the typical +%% case is probably that diameter should be started in any case; for +%% example, to be able to originate requests.) +%% +%% Moreover, attach/1 must be called to communicate the list of +%% services for which the local node is willing to handle requests. +%% The servers on different nodes communicate so that each server +%% knows which nodes are prepared to handle requests for which +%% services. +%% %% spawn_opt callbacks -export([spawn_local/2, diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index cf5e7f21d3..b86dcaf923 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.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. 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 8423e30269..4667bbc3f2 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-2019. 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. @@ -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 @@ -284,12 +285,18 @@ recv(false, false, TPid, Pkt, _, _) -> spawn_request(false, _, _, _, _, _, _) -> %% no transport discard; -%% An MFA should return the pid() of a process in which the argument -%% fun in applied, or the atom 'discard' if the fun is not applied. -%% The latter results in an acknowledgment back to the transport -%% process when appropriate, to ensure that send/recv callbacks can -%% count outstanding requests. Acknowledgement is implicit if the -%% handler process dies (in a handle_request callback for example). +%% An MFA should return the pid() of a process that invokes +%% diameter_traffic:request(ReqT), or the atom 'discard' if the +%% function is not called. The latter results in an acknowledgment +%% back to the transport process when appropriate, to ensure that +%% send/recv callbacks can count outstanding requests. Acknowledgement +%% is implicit if the handler process dies (in a handle_request +%% callback for example). +%% +%% There is no requirement that diameter be started on nodes on which +%% handler processes are spawned, just that diameter and application +%% callbacks are on the code path. (Although the MFA itself may have +%% requirements, as in the case of diameter_dist.) spawn_request(AppT, {M,F,A}, Ack, TPid, Pkt, Dict0, RecvData) -> %% Term to pass to request/1 in an appropriate process. Module %% diameter_dist implements callbacks. @@ -520,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 @@ -556,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, @@ -667,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'; @@ -1239,7 +1239,12 @@ is_result(RC, true, _) -> %% incr/2 incr(TPid, Counter) -> - diameter_stats:incr(Counter, TPid, 1). + Node = node(TPid), + if Node == node() -> + diameter_stats:incr(Counter, TPid, 1); + true -> + spawn(Node, diameter_stats, incr, [Counter, TPid, 1]) + end. %% rcc/1 @@ -1866,23 +1871,26 @@ z(#diameter_packet{header = H, bin = Bin, transport_data = T}) -> transport_data = T}. %% send/1 +%% +%% Send from a remote node using a peer connection on this one. Pkt is +%% already stripped. send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) -> Req = Req0#request{handler = self()}, - recv(TPid, Pid, TRef, zend_requezt(TPid, Pkt, Req, SvcName, Timeout)). + Pid ! recv(TPid, TRef, send_request(TPid, Pkt, Req, SvcName, Timeout)). -%% recv/4 +%% recv/3 %% %% Relay an answer from a remote node. -recv(TPid, Pid, TRef, {LocalTRef, MRef}) -> +recv(TPid, TRef, {LocalTRef, MRef}) -> receive {answer, _, _, _, _} = A -> - Pid ! A; + A; {'DOWN', MRef, process, _, _} -> - Pid ! {failover, TRef}; + {failover, TRef}; {failover = T, LocalTRef} -> - Pid ! {T, TRef}; + {T, TRef}; T -> exit({timeout, LocalTRef, TPid} = T) end. diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index bb2a4a8e92..627213637b 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -2,7 +2,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. @@ -62,7 +62,15 @@ {"2.1.4.1", [{restart_application, diameter}]}, %% 20.3.8.19 {"2.1.5", [{restart_application, diameter}]}, %% 21.0 {"2.1.6", [{restart_application, diameter}]}, %% 21.1 - {"2.2", [{update, diameter_dist, {advanced, "2.2"}}]} %% 21.3 + {"2.2", [{restart_application, diameter}]}, %% 21.3 + {"2.2.1", [{load_module, diameter}, %% 21.3.5 + {load_module, diameter_codec}, + {update, diameter_config}, + {update, diameter_dist}, + {update, diameter_peer_fsm}, + {update, diameter_service}, + {load_module, diameter_traffic}, + {update, diameter_tcp}]} ], [ {"0.9", [{restart_application, diameter}]}, @@ -106,6 +114,14 @@ {"2.1.4.1", [{restart_application, diameter}]}, {"2.1.5", [{restart_application, diameter}]}, {"2.1.6", [{restart_application, diameter}]}, - {"2.2", [{restart_application, diameter}]} + {"2.2", [{restart_application, diameter}]}, + {"2.2.1", [{load_module, diameter}, + {load_module, diameter_codec}, + {update, diameter_config}, + {update, diameter_dist}, + {update, diameter_peer_fsm}, + {update, diameter_service}, + {load_module, diameter_traffic}, + {update, diameter_tcp}]} ] }. 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/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl index e5e766d2a0..a051aeb156 100644 --- a/lib/diameter/src/transport/diameter_tcp.erl +++ b/lib/diameter/src/transport/diameter_tcp.erl @@ -569,7 +569,11 @@ m({'DOWN', M, process, P, _} = T, #monitor{parent = MRef, %% l/2 %% -%% Transition listener state. +%% Transition listener state. Or not anymore since any message causes +%% the process to exit. + +-spec l(tuple(), #listener{}) + -> no_return(). %% Service process has died. l({'DOWN', _, process, Pid, _} = T, #listener{service = Pid, diff --git a/lib/diameter/test/diameter_dist_SUITE.erl b/lib/diameter/test/diameter_dist_SUITE.erl index b2e4c35b9a..2fda2830ae 100644 --- a/lib/diameter/test/diameter_dist_SUITE.erl +++ b/lib/diameter/test/diameter_dist_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2019. All Rights Reserved. +%% Copyright Ericsson AB 2019-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. @@ -160,15 +160,32 @@ ping(Config) -> %% %% Start diameter services. -start(SvcName) - when is_atom(SvcName) -> +%% There's no need to start diameter on a node that only services +%% diameter_dist as a handler of incoming requests, but the +%% diameter_dist server must be started since the servers communicate +%% to determine who services what. The typical case is probably that +%% handler nodes also want to be able to send Diameter requests, in +%% which case the application needs to be started and diameter_dist is +%% started as a part of this, but only start the server here to ensure +%% everything still works as expected. +start({_SvcName, [_, {S1, _}, {S2, _}, _]}) + when node() == S1; %% server1 + node() == S2 -> %% server2 + Mod = diameter_dist, + {ok, _} = gen_server:start({local, Mod}, Mod, _Args = [], _Opts = []), + ok; + +start({SvcName, [{S0, _}, _, _, {C, _}]}) + when node() == S0; %% server0 + node() == C -> %% client ok = diameter:start(), ok = diameter:start_service(SvcName, ?SERVICE((?L(SvcName)))); -start(Config) -> +start(Config) + when is_list(Config) -> Nodes = ?util:read_priv(Config, nodes), [] = [{N,RC} || {N,S} <- Nodes, - RC <- [rpc:call(N, ?MODULE, start, [S])], + RC <- [rpc:call(N, ?MODULE, start, [{S, Nodes}])], RC /= ok]. sequence() -> @@ -194,13 +211,12 @@ origin(Server) -> %% Establish one connection from the client, terminated on the first %% server node, the others handling requests. -connect({?SERVER, Config, [{Node, _} | _]}) -> - if Node == node() -> %% server0 - ?util:write_priv(Config, lref, {Node, ?util:listen(?SERVER, tcp)}); - true -> - diameter_dist:attach([?SERVER]) - end, - ok; +connect({?SERVER, Config, [{Node, _} | _]}) + when Node == node() -> %% server0 + ok = ?util:write_priv(Config, lref, {Node, ?util:listen(?SERVER, tcp)}); + +connect({?SERVER, _Config, _}) -> %% server[12]: register to receive requests + ok = diameter_dist:attach([?SERVER]); connect({?CLIENT, Config, _}) -> ?util:connect(?CLIENT, tcp, ?util:read_priv(Config, lref)), @@ -239,7 +255,18 @@ send(Config) -> send(Config, 0, Dict) -> [{Server0, _} | _] = ?util:read_priv(Config, nodes) , Node = atom_to_binary(Server0, utf8), - {false, _} = {dict:is_key(Node, Dict), dict:to_list(Dict)}; + {false, _} = {dict:is_key(Node, Dict), dict:to_list(Dict)}, + %% Check that counters have been incremented as expected on server0. + [Info] = rpc:call(Server0, diameter, service_info, [?SERVER, connections]), + {[Stats], _} = {[S || {statistics, S} <- Info], Info}, + {[{recv, 1, 100}, {send, 0, 100}], _} + = {[{D,R,N} || T <- [recv, send], + {{{0,275,R}, D}, N} <- Stats, + D == T], + Stats}, + {[{send, 0, 100, 2001}], _} + = {[{D,R,N,C} || {{{0,275,R}, D, {'Result-Code', C}}, N} <- Stats], + Stats}; send(Config, N, Dict) -> #diameter_base_STA{'Result-Code' = ?SUCCESS, 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), |