diff options
Diffstat (limited to 'lib/diameter')
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), |