diff options
Diffstat (limited to 'lib/diameter')
48 files changed, 3059 insertions, 978 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 379e9f0738..318c98f786 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -1,5 +1,7 @@ <?xml version="1.0" encoding="latin1" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY nodes + '<seealso marker="erts:erlang#nodes-0">erlang:nodes/0</seealso>'> <!ENTITY make_ref '<seealso marker="erts:erlang#make_ref-0">erlang:make_ref/0</seealso>'> <!ENTITY transport_module @@ -41,7 +43,7 @@ under the License. <approved></approved> <checked></checked> <date></date> -<rev>%VSN%</rev> +<rev></rev> <file>diameter.xml</file> </header> @@ -341,8 +343,9 @@ Has one of the following types.</p> An address list is available to the start function of a &transport_module;, which can return a new list for use in the subsequent CER or CEA. -Host-IP-Address need not be specified if the transport start function -returns an address list.</p> +Host-IP-Address need not be specified if the transport module in +question communicates an address list as described in +&man_transport;</p> </item> <tag><c>{'Vendor-Id', &dict_Unsigned32;}</c></tag> @@ -772,16 +775,16 @@ Application-Id AVP's in particular.</p> | evaluable()}</c></tag> <item> <p> -Specifies the degree to which multiple transport connections to the -same peer are accepted by the service.</p> +Specifies the degree to which the service allows multiple transport +connections to the same peer.</p> <p> If type <c>[node()]</c> then a connection is rejected if another already exists on any of the specified nodes. -Values of type <c>false</c>, <c>node</c>, <c>nodes</c> or +Types <c>false</c>, <c>node</c>, <c>nodes</c> and &evaluable; are equivalent to -values <c>[]</c>, <c>[node()]</c>, <c>[node()|nodes()]</c> and the -evaluated value, respectively, evaluation of each expression taking +<c>[]</c>, <c>[node()]</c>, <c>[node()|nodes()]</c> and the +evaluated value respectively, evaluation of each expression taking place whenever a new connection is to be established. Note that <c>false</c> allows an unlimited number of connections to be established with the same peer.</p> @@ -819,6 +822,88 @@ non-negative integer less than <c>1 bsl (32-N)</c>.</p> <p> Defaults to <c>{0,32}</c>.</p> + +<warning> +<p> +Multiple Erlang nodes implementing the same Diameter node should +be configured with different sequence masks to ensure that each node +uses a unique range of End-to-End and Hop-by-Hop identifiers for +outgoing requests.</p> +</warning> +</item> + +<tag><c>{share_peers, boolean() | [node()] | evaluable()}</c></tag> +<item> +<p> +Specifies nodes to which peer connections established on the local +Erlang node are communicated. +Shared peers become available in the remote candidates list passed to +&app_pick_peer; callbacks on remote nodes whose services are +configured to use them: see <c>use_shared_peers</c> below.</p> + +<p> +If <c>false</c> then peers are not shared. +If <c>[node()]</c> then peers are shared with the specified list of +nodes. +If <c>evaluable()</c> then peers are shared with the nodes returned +by the specified function, evaluated whenever a peer connection +becomes available or a remote service requests information about local +connections. +The value <c>true</c> is equivalent to <c>fun &nodes;</c>. +The value <c>node()</c> in a node list is ignored, so a collection of +services can all be configured to share with the same list of +nodes.</p> + +<p> +Defaults to <c>false</c>.</p> + +<note> +<p> +Peers are only shared with services of the same name for the purpose +of sending outgoing requests. +Since the value of the &application_opt; <c>alias</c>, passed to +&call;, is the handle for identifying a peer as a suitable +candidate, services that share peers must use the same aliases to +identify their supported applications. +They should typically also configure identical &capabilities;, since +by sharing peer connections they are distributing the implementation +of a single Diameter node across multiple Erlang nodes.</p> +</note> +</item> + +<tag><c>{use_shared_peers, boolean() | [node()] | evaluable()}</c></tag> +<item> +<p> +Specifies nodes from which communicated peers are made available in +the remote candidates list of &app_pick_peer; callbacks.</p> + +<p> +If <c>false</c> then remote peers are not used. +If <c>[node()]</c> then only peers from the specified list of nodes +are used. +If <c>evaluable()</c> then only peers returned by the specified +function are used, evaluated whenever a remote service communicates +information about an available peer connection. +The value <c>true</c> is equivalent to <c>fun &nodes;</c>. +The value <c>node()</c> in a node list is ignored.</p> + +<p> +Defaults to <c>false</c>.</p> + +<note> +<p> +A service that does not use shared peers will always pass the empty +list as the second argument of &app_pick_peer; callbacks.</p> +</note> + +<warning> +<p> +Sending a request over a peer connection on a remote node is less +efficient than sending it over a local connection. +It may be preferable to make use of the &service_opt; +<c>restrict_connections</c> and maintain a dedicated connection on +each node from which requests are sent.</p> +</warning> </item> </taglist> @@ -1078,7 +1163,7 @@ transport.</p> <marker id="transport_config"/> <tag><c>{transport_config, term()}</c></tag> -<tag><c>{transport_config, term(), &dict_Unsigned32;}</c></tag> +<tag><c>{transport_config, term(), &dict_Unsigned32; | infinity}</c></tag> <item> <p> A term passed as the third argument to the &transport_start; function of @@ -1125,6 +1210,30 @@ modules in order until one establishes a connection within the corresponding timeout (see below) or all fail.</p> </item> +<marker id="watchdog_config"/> +<tag><c>{watchdog_config, [{okay|suspect, non_neg_integer()}]}</c></tag> +<item> +<p> +Specifies configuration that alters the behaviour of the watchdog +state machine. +On key <c>okay</c>, the non-negative number of answered DWR +messages before transitioning from REOPEN to OKAY. +On key <c>suspect</c>, the number of watchdog timeouts before +transitioning from OKAY to SUSPECT when DWR is unanswered, or 0 to +not make the transition.</p> + +<p> +Defaults to <c>[{okay, 3}, {suspect, 1}]</c>. +Not specifying a key is equivalent to specifying +the default value for that key.</p> +<warning> +<p> +The default value is as required by RFC 3539: changing it results +in non-standard behaviour that should only be used to simulate +misbehaving nodes during test.</p> +</warning> +</item> + <marker id="watchdog_timer"/> <tag><c>{watchdog_timer, TwInit}</c></tag> <item> diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml index d0f1b22ebd..e6c9cc9a90 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -37,7 +37,7 @@ under the License. <approved></approved> <checked></checked> <date></date> -<rev>%REV%</rev> +<rev></rev> <file>diameter_app.xml</file> </header> @@ -196,7 +196,8 @@ process.</p> </type> <desc> <p> -Invoked to signal the availability of a peer connection. +Invoked to signal the availability of a peer connection on the local +Erlang node. In particular, capabilities exchange with the peer has indicated support for the application in question, the RFC 3539 watchdog state machine for the connection has reached state <c>OKAY</c> and Diameter @@ -230,8 +231,8 @@ handled independently of &peer_up; and &peer_down;.</p> </type> <desc> <p> -Invoked to signal that a peer connection is no longer available -following a previous call to &peer_up;. +Invoked to signal that a peer connection on the local Erlang node is +no longer available following a previous call to &peer_up;. In particular, that the RFC 3539 watchdog state machine for the connection has left state <c>OKAY</c> and the peer will no longer be a candidate in &pick_peer; callbacks.</p> @@ -240,11 +241,11 @@ candidate in &pick_peer; callbacks.</p> </func> <func> -<name>Mod:pick_peer(Candidates, _Reserved, SvcName, State) +<name>Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State) -> Selection | false</name> <fsummary>Select a target peer for an outgoing request.</fsummary> <type> -<v>Candidates = [&peer;]</v> +<v>LocalCandidates = RemoteCandidates = [&peer;]</v> <v>SvcName = &mod_service_name;</v> <v>State = NewState = &state;</v> <v>Selection = {ok, Peer} | {Peer, NewState}</v> @@ -257,7 +258,7 @@ peer for an outgoing request. The return value indicates the selected peer.</p> <p> -The candidate list contains only those peers that have advertised +The candidate lists contain only those peers that have advertised support for the Diameter application in question during capabilities exchange, that have not be excluded by a <c>filter</c> option in the call to &mod_call; @@ -266,7 +267,11 @@ The order of the elements is unspecified except that any peers whose Origin-Host and Origin-Realm matches that of the outgoing request (in the sense of a <c>{filter, {all, [host, realm]}}</c> option to &mod_call;) -will be placed at the head of the list.</p> +will be placed at the head of the list. +<c>LocalCandidates</c> contains peers whose transport process resides +on the local Erlang node while +<c>RemoteCandidates</c> contains peers that have been communicated +from other nodes by services of the same name.</p> <p> A callback that returns a peer() will be followed by a @@ -286,16 +291,19 @@ retransmission to an alternate peer is abandoned if an answer is received from a previously selected peer.</p> <p> -Returning <c>false</c> or <c>{false, NewState}</c> causes <c>{error, -no_connection}</c> to be returned from &mod_call;.</p> - -<p> The return values <c>false</c> and <c>{false, State}</c> (that is, <c>NewState = State</c>) are equivalent, as are <c>{ok, Peer}</c> and <c>{Peer, State}</c>.</p> <note> <p> +The &mod_service_opt; <c>use_shared_peers</c> determines whether or +not a service uses peers shared from other nodes. +If not then <c>RemoteCandidates</c> is the empty list.</p> +</note> + +<warning> +<p> The return value <c>{Peer, NewState}</c> is only allowed if the Diameter application in question was configured with the &mod_application_opt; <c>{call_mutates_state, true}</c>. @@ -303,7 +311,7 @@ Otherwise, the <c>State</c> argument is always the intial value as configured on the application, not any subsequent value returned by a &peer_up; or &peer_down; callback.</p> -</note> +</warning> </desc> @@ -557,7 +565,8 @@ Equivalent to</p> </pre> <p> where <c>Avps</c> sets the Origin-Host, Origin-Realm, the specified -Result-Code and (if the request contained one) Session-Id AVP's.</p> +Result-Code and (if the request contained one) Session-Id AVP's, and +possibly Failed-AVP as described below.</p> <p> Returning a value other than 3xxx or 5xxx will cause the request @@ -565,6 +574,14 @@ process in question to fail, as will returning a 5xxx value if the peer connection in question has been configured with the RFC 3588 common dictionary <c>diameter_gen_base_rfc3588</c>. (Since RFC 3588 only allows 3xxx values in an answer-message.)</p> + +<p> +When returning 5xxx, Failed-AVP will be populated with the AVP of the +first matching Result-Code/AVP pair in the <c>errors</c> field of the +argument &packet;, if found. +If this is not appropriate then an answer-message should be +constructed explicitly and returned in a <c>reply</c> tuple +instead.</p> </item> <tag><c>{relay, Opts}</c></tag> @@ -584,8 +601,7 @@ header of the relayed request.</p> The returned <c>Opts</c> should not specify <c>detach</c>. A subsequent &handle_answer; callback for the relayed request must return its first -argument, the <c>#diameter_packet{}</c> record containing the answer -message. +argument, the &packet; containing the answer message. Note that the <c>extra</c> option can be specified to supply arguments that can distinguish the relay case from others if so desired. Any other return value (for example, from a diff --git a/lib/diameter/doc/src/diameter_compile.xml b/lib/diameter/doc/src/diameter_compile.xml index 0bd7ad1789..fc81e4efed 100644 --- a/lib/diameter/doc/src/diameter_compile.xml +++ b/lib/diameter/doc/src/diameter_compile.xml @@ -11,7 +11,7 @@ <comref> <header> <copyright> -<year>2011</year><year>2012</year> +<year>2011</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -59,6 +59,7 @@ The module &man_make; provides an alternate compilation interface.</p> Compile a single dictionary file to Erlang source. Valid options are as follows.</p> +<taglist> <tag><![CDATA[-i <dir>]]></tag> <item> <p> @@ -71,7 +72,6 @@ dependency, not an erl/hrl dependency.</p> Multiple <c>-i</c> options can be specified.</p> </item> -<taglist> <tag><![CDATA[-o <dir>]]></tag> <item> <p> @@ -90,18 +90,30 @@ Supress erl and hrl generation, respectively.</p> <tag><![CDATA[--prefix <prefix>]]></tag> <item> <p> -Set <c>&dict_name;</c> or <c>&dict_prefix;</c> to the specified -string. -Overrides any setting in the file itself.</p> +Transform the input dictionary before compilation, setting +<c>&dict_name;</c> or <c>&dict_prefix;</c> to the specified +string.</p> </item> -<tag><![CDATA[--inherits <dict>]]></tag> +<tag><![CDATA[--inherits <arg>]]></tag> <item> <p> -Append &dict_inherits; of the specified module. -Specifying <c>"-"</c> has the effect of discarding clearing any -previous inherits, both in the dictionary file and on the options -list.</p> +Transform the input dictionary before compilation, appending +<c>&dict_inherits;</c> of the specified string.</p> + +<p> +Two forms of <c>--inherits</c> have special meaning:</p> + +<pre> +--inherits - +--inherits Prev/Mod +</pre> + +<p> +The first has the effect of clearing any previous inherits, the second +of replacing a previous inherits of <c>Prev</c> to one of <c>Mod</c>. +This allows the semantics of the input dictionary to be changed without +modifying the file itself.</p> <p> Multiple <c>--inherits</c> options can be specified.</p> diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 1034781ff2..419dc143af 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -263,15 +263,14 @@ dictionary's definitions but the former makes for easier reuse.</p> <p> All dictionaries should typically inherit &the_rfc; AVPs from -<c>diameter_gen_base_rfc3588</c>.</p> +<c>diameter_gen_base_rfc6733</c>.</p> <p> Example:</p> <pre> -@inherits diameter_gen_base_rfc3588 +@inherits diameter_gen_base_rfc6733 </pre> - </item> <marker id="avp_types"/> diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml index da6124310e..ec71251be1 100644 --- a/lib/diameter/doc/src/diameter_make.xml +++ b/lib/diameter/doc/src/diameter_make.xml @@ -14,6 +14,7 @@ <header> <copyright> <year>2012</year> +<year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -73,7 +74,7 @@ Compile a single dictionary file to Erlang source. <taglist> -<tag><c>{include, Dir::string()}</c></tag> +<tag><c>{include, string()}</c></tag> <item> <p> Prepend the specified directory to the code path. @@ -85,7 +86,7 @@ dependency, not an erl/hrl dependency.</p> Multiple <c>include</c> options can be specified.</p> </item> -<tag><c>{outdir, Dir::string()}</c></tag> +<tag><c>{outdir, string()}</c></tag> <item> <p> Write generated source to the specified directory. @@ -95,18 +96,30 @@ Defaults to the current working directory.</p> <tag><c>{name|prefix, string()}</c></tag> <item> <p> -Set <c>&dict_name;</c> or <c>&dict_prefix;</c> to the specified -string. -Overrides any setting in the file itself.</p> +Transform the input dictionary before compilation, setting +<c>&dict_name;</c> or <c>&dict_prefix;</c> to the specified +string.</p> </item> -<tag><c>{inherits, Mod::string()}</c></tag> +<tag><c>{inherits, string()}</c></tag> <item> <p> -Append &dict_inherits; of the specified module. -Specifying <c>"-"</c> has the effect of discarding clearing any -previous inherits, both in the dictionary file and on the options -list.</p> +Transform the input dictionary before compilation, appending +<c>&dict_inherits;</c> of the specified string.</p> + +<p> +Two forms of <c>@inherits</c> have special meaning:</p> + +<pre> +{inherits, "-"} +{inherits, "Prev/Mod"} +</pre> + +<p> +The first has the effect of clearing any previous inherits, the second +of replacing a previous inherits of <c>Prev</c> to one of <c>Mod</c>. +This allows the semantics of the input dictionary to be changed without +modifying the file itself.</p> <p> Multiple <c>inherits</c> options can be specified.</p> @@ -126,8 +139,9 @@ Multiple <c>inherits</c> options can be specified.</p> <p> All options are string-valued. -In particular, it is not currently possible to -an &dict_inherits; module as an atom() or a path as a &filename;</p> +In particular, it is not currently possible to specify +an &dict_inherits; module as an atom(), or a path as an arbitrary +&filename;</p> </section> diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml index 01c781d553..8e509aa829 100644 --- a/lib/diameter/doc/src/diameter_tcp.xml +++ b/lib/diameter/doc/src/diameter_tcp.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="latin1" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY start '<seealso marker="#start-3">start/3</seealso>'> <!ENTITY gen_tcp_connect3 '<seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect/3</seealso>'> <!ENTITY gen_tcp_listen2 @@ -81,7 +82,9 @@ before configuring TLS capability on diameter transports.</p> <func> <name>start({Type, Ref}, Svc, [Opt]) - -> {ok, Pid, [LAddr]} | {error, Reason}</name> + -> {ok, Pid} + | {ok, Pid, [LAddr]} + | {error, Reason}</name> <fsummary>Start a transport process.</fsummary> <type> <v>Type = connect | accept</v> @@ -153,13 +156,14 @@ that will not be forthcoming, which will eventually cause the RFC 3539 watchdog to take down the connection.</p> <p> -If the <c>#diameter_service{}</c> record has more than one -<c>Host-IP-Address</c> and option <c>ip</c> is unspecified then the -first of the these addresses is used as the local address.</p> - -<p> -The returned local address list has length one.</p> - +If an <c>ip</c> option is not specified then the first element of a +non-empty <c>Host-IP-Address</c> list in <c>Svc</c> provides the local +IP address. +If neither is specified then the default address selected by &gen_tcp; +is used. +In all cases, the selected address is either returned from +&start; or passed in a <c>connected</c> message over the transport +interface.</p> </desc> </func> diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml index 55b531155f..9161bd1f48 100644 --- a/lib/diameter/doc/src/diameter_transport.xml +++ b/lib/diameter/doc/src/diameter_transport.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="latin1" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd" [ <!ENTITY message '<seealso marker="#message">message()</seealso>'> + <!ENTITY MESSAGES '<seealso marker="#MESSAGES">MESSAGES</seealso>'> + <!ENTITY start '<seealso marker="#Mod:start-3">start/3</seealso>'> <!ENTITY ip_address '<seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>'> <!ENTITY % also SYSTEM "seealso.ent" > @@ -12,7 +14,7 @@ <erlref> <header> <copyright> -<year>2011</year><year>2012</year> +<year>2011</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -125,7 +127,7 @@ Ref is the value that was returned from the call to &mod_add_transport; that has lead to starting of a transport process.</p> <p> -<c>Svc</c> contains the capabilities passed to &mod_start_service; and +<c>Svc</c> contains capabilities passed to &mod_start_service; and &mod_add_transport;, values passed to the latter overriding those passed to the former.</p> @@ -134,13 +136,15 @@ passed to the former.</p> &mod_transport_opt; list passed to &mod_add_transport;.</p> <p> -The start function should use the <c>Host-IP-Address</c> list and/or -<c>Config</c> to select an appropriate list of local IP addresses, -and should return this list if different from the -<c>#diameter_service{}</c> addresses. -The returned list is used to populate <c>Host-IP-Address</c> AVPs in -outgoing capabilities exchange messages, the -<c>#diameter_service{}</c> addresses being used otherwise.</p> +The start function should use the <c>Host-IP-Address</c> list in +<c>Svc</c> and/or <c>Config</c> to select and return an appropriate +list of local IP addresses. +In the connecting case, the local address list can instead be +communicated in a <c>connected</c> message (see &MESSAGES; below) +following connection establishment. +In either case, the local address list is used to populate +<c>Host-IP-Address</c> AVPs in outgoing capabilities exchange +messages if <c>Host-IP-Address</c> is unspecified.</p> <p> A transport process must implement the message interface documented below. @@ -230,13 +234,16 @@ Not sent if the transport process has <c>Type=connect</c>.</p> </item> <tag><c>{diameter, {self(), connected, Remote}}</c></tag> +<tag><c>{diameter, {self(), connected, Remote, [LocalAddr]}}</c></tag> <item> <p> Inform the parent that the transport process with <c>Type=connect</c> has established a connection with a peer. -Not sent if the transport process has <c>Type=accept</c>. +Not sent if the transport process has <c>Type=accept</c>. <c>Remote</c> is an arbitrary term that uniquely identifies the remote -endpoint to which the transport has connected.</p> +endpoint to which the transport has connected. +A <c>LocalAddr</c> list has the same semantics as one returned from +&start;.</p> </item> <tag><c>{diameter, {recv, &message;}}</c></tag> diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index d63e2021c8..ad61f12b5b 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -42,7 +42,44 @@ first.</p> <!-- ===================================================================== --> -<section><title>Diameter 1.4.1</title> +<section><title>diameter 1.4.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix broken Vendor-Specific-Application-Id configuration.</p> + <p> + RFC 6733 changed the definition of this Grouped AVP, + changing the arity of Vendor-Id from 1* to 1. A component + Vendor-Id can now be either list- or integer-valued in + service and transport configuration, allowing it to be + used with both RFC 3588 and RFC 6733 dictionaries.</p> + <p> + Own Id: OTP-10942</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add transport_opt() watchdog_config to allow non-standard + behaviour of the watchdog state machine.</p> + <p> + This can be useful during test but should not be used on + nodes that must conform to RFC 3539.</p> + <p> + Own Id: OTP-10898</p> + </item> + </list> + </section> + +</section> + +<section><title>diameter 1.4.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -129,7 +166,7 @@ first.</p> </section> -<section><title>Diameter 1.4</title> +<section><title>diameter 1.4</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -208,7 +245,7 @@ first.</p> </section> -<section><title>Diameter 1.3.1</title> +<section><title>diameter 1.3.1</title> <section><title>Known Bugs and Problems</title> <list> @@ -224,7 +261,7 @@ first.</p> </section> -<section><title>Diameter 1.3</title> +<section><title>diameter 1.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -381,7 +418,7 @@ first.</p> </section> -<section><title>Diameter 1.2</title> +<section><title>diameter 1.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -481,7 +518,7 @@ first.</p> </section> -<section><title>Diameter 1.1</title> +<section><title>diameter 1.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -508,7 +545,7 @@ first.</p> </section> -<section><title>Diameter 1.0</title> +<section><title>diameter 1.0</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -622,7 +659,7 @@ first.</p> </section> -<section><title>Diameter 0.10</title> +<section><title>diameter 0.10</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/diameter/examples/code/peer.erl b/lib/diameter/examples/code/peer.erl index 8fdeba57bf..b4ee17e4b7 100644 --- a/lib/diameter/examples/code/peer.erl +++ b/lib/diameter/examples/code/peer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -51,7 +51,6 @@ | {protocol(), ip_address(), non_neg_integer()} | {protocol(), ip_address(), ip_address(), non_neg_integer()}. --define(DEFAULT_ADDR, {127,0,0,1}). -define(DEFAULT_PORT, 3868). %% --------------------------------------------------------------------------- @@ -111,7 +110,7 @@ server({T, Addr, Port}) -> {port, Port}]}]; server(T) -> - server({T, ?DEFAULT_ADDR, ?DEFAULT_PORT}). + server({T, loopback, ?DEFAULT_PORT}). %% client/1 %% @@ -124,21 +123,28 @@ client({all, LA, RA, RP}) -> client({T, LA, RA, RP}) -> [{transport_module, tmod(T)}, - {transport_config, [{ip, addr(LA)}, - {raddr, addr(RA)}, + {transport_config, [{raddr, addr(RA)}, {rport, RP}, - {reuseaddr, true}]}]; + {reuseaddr, true} + | ip(LA)]}]; -client({T, LA, RP}) -> - client({T, LA, LA, RP}); +client({T, RA, RP}) -> + client({T, default, RA, RP}); client(T) -> - client({T, ?DEFAULT_ADDR, ?DEFAULT_ADDR, ?DEFAULT_PORT}). + client({T, loopback, loopback, ?DEFAULT_PORT}). tmod(tcp) -> diameter_tcp; tmod(sctp) -> diameter_sctp. -addr(default) -> - ?DEFAULT_ADDR; +ip(default) -> + []; +ip(loopback) -> + [{ip, {127,0,0,1}}]; +ip(Addr) -> + [{ip, Addr}]. + +addr(loopback) -> + {127,0,0,1}; addr(A) -> A. diff --git a/lib/diameter/examples/code/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl index da31add70d..69836774a1 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-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -20,7 +20,7 @@ -module(redirect_cb). -include_lib("diameter/include/diameter.hrl"). --include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). +-include_lib("diameter/include/diameter_gen_base_rfc3588.hrl"). %% diameter callbacks -export([peer_up/3, diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 03aa557c2e..55aae3a243 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -25,11 +25,23 @@ -define(THROW(T), throw({?MODULE, T})). -%%% --------------------------------------------------------------------------- -%%% # encode_avps/3 -%%% -%%% Returns: binary() -%%% --------------------------------------------------------------------------- +-type parent_name() :: atom(). %% parent = Message or AVP +-type parent_record() :: tuple(). %% +-type avp_name() :: atom(). +-type avp_record() :: tuple(). +-type avp_values() :: [{avp_name(), term()}]. + +-type non_grouped_avp() :: #diameter_avp{}. +-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]). +-type avp() :: non_grouped_avp() | grouped_avp(). + +%% --------------------------------------------------------------------------- +%% # encode_avps/2 +%% --------------------------------------------------------------------------- + +-spec encode_avps(parent_name(), parent_record() | avp_values()) + -> binary() + | no_return(). encode_avps(Name, Vals) when is_list(Vals) -> @@ -129,42 +141,26 @@ pack_AVP(Name, #diameter_avp{name = AvpName, orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]), e(AvpName, [Data]). -%%% --------------------------------------------------------------------------- -%%% # decode_avps/2 -%%% -%%% Returns: {Rec, Avps, Failed} -%%% -%%% Rec = decoded message record -%%% Avps = list of Avp -%%% Failed = list of {ResultCode, #diameter_avp{}} -%%% -%%% Avp = #diameter_avp{} if type is not Grouped -%%% | list of Avp where first element has type Grouped -%%% and following elements are its component -%%% AVP's. -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # decode_avps/2 +%% --------------------------------------------------------------------------- + +-spec decode_avps(parent_name(), [#diameter_avp{}]) + -> {parent_record(), [avp()], Failed} + when Failed :: [{5000..5999, #diameter_avp{}}]. decode_avps(Name, Recs) -> - d_rc(Name, lists:foldl(fun(T,A) -> decode(Name, T, A) end, - {[], {newrec(Name), []}}, - Recs)). + {Avps, {Rec, Failed}} + = lists:foldl(fun(T,A) -> decode(Name, T, A) end, + {[], {newrec(Name), []}}, + Recs), + {Rec, Avps, Failed ++ missing(Rec, Name)}. +%% Append 5005 errors so that a 5014 for the same AVP will take +%% precedence in a Result-Code/Failed-AVP setting. newrec(Name) -> '#new-'(name2rec(Name)). -%% No errors so far: keep looking. -d_rc(Name, {Avps, {Rec, [] = Failed}}) -> - try - true = have_required_avps(Rec, Name), - {Rec, Avps, Failed} - catch - throw: {?MODULE, {AvpName, Reason}} -> - diameter_lib:log({decode, error}, - ?MODULE, - ?LINE, - {AvpName, Reason, Rec}), - {Rec, Avps, [{5005, empty_avp(AvpName)}]} - end; %% 3588: %% %% DIAMETER_MISSING_AVP 5005 @@ -175,9 +171,17 @@ d_rc(Name, {Avps, {Rec, [] = Failed}}) -> %% Vendor-Id if applicable. The value field of the missing AVP %% should be of correct minimum length and contain zeroes. -%% Or not. Only need to report the first error so look no further. -d_rc(_, {Avps, {Rec, Failed}}) -> - {Rec, Avps, lists:reverse(Failed)}. +missing(Rec, Name) -> + [{5005, empty_avp(F)} || F <- '#info-'(element(1, Rec), fields), + A <- [avp_arity(Name, F)], + false <- [have_arity(A, '#get-'(F, Rec))]]. + +%% Maximum arities have already been checked in building the record. + +have_arity({Min, _}, L) -> + Min =< length(L); +have_arity(N, V) -> + N /= 1 orelse V /= undefined. %% empty_avp/1 @@ -192,25 +196,6 @@ empty_avp(Name) -> data = empty_value(Name), type = Type}. -%% have_required_avps/2 - -have_required_avps(Rec, Name) -> - lists:foreach(fun(F) -> hra(Name, F, Rec) end, - '#info-'(element(1, Rec), fields)), - true. - -hra(Name, AvpName, Rec) -> - Arity = avp_arity(Name, AvpName), - hra(Arity, '#get-'(AvpName, Rec)) - orelse ?THROW({AvpName, {insufficient_arity, Arity}}). - -%% Maximum arities have already been checked in building the record. - -hra({Min, _}, L) -> - Min =< length(L); -hra(N, V) -> - N /= 1 orelse V /= undefined. - %% 3588, ch 7: %% %% The Result-Code AVP describes the error that the Diameter node @@ -227,23 +212,22 @@ decode(Name, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) -> %% decode/4 -%% Don't know this AVP: see if it can be packed in an 'AVP' field -%% undecoded, unless it's mandatory. Need to give Failed-AVP special -%% treatment since it'll contain any unrecognized mandatory AVP's. -decode(Name, 'AVP', #diameter_avp{is_mandatory = M} = Avp, {Avps, Acc}) -> - {[Avp | Avps], if M, Name /= 'Failed-AVP' -> - unknown(Avp, Acc); - true -> - pack_AVP(Name, Avp, Acc) - end}; -%% Note that the type field is 'undefined' in this case. - -%% Or try to decode. decode(Name, {AvpName, Type}, Avp, Acc) -> - d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc). + d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc); + +decode(Name, 'AVP', Avp, Acc) -> + decode_AVP(Name, Avp, Acc). %% d/3 +%% Don't try to decode the value of a Failed-AVP component since it +%% probably won't. Note that matching on 'Failed-AVP' assumes that +%% this is the RFC AVP, with code 279. Strictly, this doesn't need to +%% be the case, so we're assuming no one defines another Failed-AVP. +d('Failed-AVP' = Name, Avp, Acc) -> + decode_AVP(Name, Avp, Acc); + +%% Or try to decode. d(Name, Avp, {Avps, Acc}) -> #diameter_avp{name = AvpName, data = Data} @@ -265,17 +249,25 @@ d(Name, Avp, {Avps, Acc}) -> ?LINE, {Reason, Avp, erlang:get_stacktrace()}), {Rec, Failed} = Acc, - {[Avp|Avps], {Rec, [{rc(Reason), Avp} | Failed]}} + {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}} end. +%% decode_AVP/3 +%% +%% Don't know this AVP: see if it can be packed in an 'AVP' field +%% undecoded. Note that the type field is 'undefined' in this case. + +decode_AVP(Name, Avp, {Avps, Acc}) -> + {[Avp | Avps], pack_AVP(Name, Avp, Acc)}. + %% rc/1 %% diameter_types will raise an error of this form to communicate %% DIAMETER_INVALID_AVP_LENGTH (5014). A module specified to a %% @custom_types tag in a spec file can also raise an error of this %% form. -rc({'DIAMETER', RC, _}) -> - RC; +rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = Avp) -> + {RC, Avp#diameter_avp{data = empty_value(AvpName)}}; %% 3588: %% @@ -283,20 +275,13 @@ rc({'DIAMETER', RC, _}) -> %% The request contained an AVP with an invalid value in its data %% portion. A Diameter message indicating this error MUST include %% the offending AVPs within a Failed-AVP AVP. -rc(_) -> - 5004. +rc(_, Avp) -> + {5004, Avp}. %% ungroup/2 -%% -%% Returns: {Avp, Dec} -%% -%% Avp = #diameter_avp{} if type is not Grouped -%% | list of Avp where first element has type Grouped -%% and following elements are its component -%% AVP's. -%% = as for decode_avps/2 -%% -%% Dec = #diameter_avp{}, either Avp or its head in the list case. + +-spec ungroup(term(), #diameter_avp{}) + -> {avp(), #diameter_avp{}}. %% The decoded value in the Grouped case is as returned by grouped_avp/3: %% a record and a list of component AVP's. @@ -325,10 +310,18 @@ pack_avp(_, Arity, Avp, Acc) -> %% pack_AVP/3 -pack_AVP(Name, Avp, Acc) -> +%% Give Failed-AVP special treatment since it'll contain any +%% unrecognized mandatory AVP's. +pack_AVP(Name, #diameter_avp{is_mandatory = true} = Avp, Acc) + when Name /= 'Failed-AVP' -> + {Rec, Failed} = Acc, + {Rec, [{5001, Avp} | Failed]}; + +pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> case avp_arity(Name, 'AVP') of 0 -> - unknown(Avp, Acc); + {Rec, Failed} = Acc, + {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; Arity -> pack(Arity, 'AVP', Avp, Acc) end. @@ -345,9 +338,6 @@ pack_AVP(Name, Avp, Acc) -> %% A message was received with an AVP that MUST NOT be present. The %% Failed-AVP AVP MUST be included and contain a copy of the %% offending AVP. -%% -unknown(#diameter_avp{is_mandatory = B} = Avp, {Rec, Failed}) -> - {Rec, [{if B -> 5001; true -> 5008 end, Avp} | Failed]}. %% pack/4 @@ -386,23 +376,29 @@ value('AVP', Avp) -> value(_, Avp) -> Avp#diameter_avp.value. -%%% --------------------------------------------------------------------------- -%%% # grouped_avp/3 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # grouped_avp/3 +%% --------------------------------------------------------------------------- + +-spec grouped_avp(decode, avp_name(), binary()) + -> {avp_record(), [avp()]}; + (encode, avp_name(), avp_record() | avp_values()) + -> binary() + | no_return(). grouped_avp(decode, Name, Data) -> {Rec, Avps, []} = decode_avps(Name, diameter_codec:collect_avps(Data)), {Rec, Avps}; -%% Note that a failed match here will result in 5004. Note that this is -%% the only AVP type that doesn't just return the decoded value, also -%% returning the list of component #diameter_avp{}'s. +%% A failed match here will result in 5004. Note that this is the only +%% AVP type that doesn't just return the decoded record, also +%% returning the list of component AVP's. grouped_avp(encode, Name, Data) -> encode_avps(Name, Data). -%%% --------------------------------------------------------------------------- -%%% # empty_group/1 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # empty_group/1 +%% --------------------------------------------------------------------------- empty_group(Name) -> list_to_binary(empty_body(Name)). @@ -423,9 +419,9 @@ z(Name) -> Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)), << <<0>> || <<_>> <= Bin >>. -%%% --------------------------------------------------------------------------- -%%% # empty/1 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # empty/1 +%% --------------------------------------------------------------------------- empty(AvpName) -> avp(encode, zero, AvpName). diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index df10c33268..c0cf418f06 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -230,7 +230,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk # Can't $(INSTALL_DIR) more than one directory at a time on Solaris. release_spec: opt - for d in bin ebin include src/dict; do \ + for d in bin ebin examples include src/dict; do \ $(INSTALL_DIR) "$(RELSYSDIR)/$$d"; \ done $(INSTALL_SCRIPT) $(BINS:%=../bin/%) "$(RELSYSDIR)/bin" diff --git a/lib/diameter/src/base/diameter.appup.src b/lib/diameter/src/base/diameter.appup.src index 2ce89579ff..359f434941 100644 --- a/lib/diameter/src/base/diameter.appup.src +++ b/lib/diameter/src/base/diameter.appup.src @@ -28,7 +28,13 @@ {"1.2.1", [{restart_application, diameter}]}, {"1.3", [{restart_application, diameter}]}, %% R15B03 {"1.3.1", [{restart_application, diameter}]}, - {"1.4", [{restart_application, diameter}]} %% R16A + {"1.4", [{restart_application, diameter}]}, %% R16A + {"1.4.1", [{load_module, diameter_reg}, %% R16B + {load_module, diameter_stats}, + {load_module, diameter_service}, + {load_module, diameter_watchdog}, + {load_module, diameter_capx}, + {load_module, diameter}]} ], [ {"0.9", [{restart_application, diameter}]}, @@ -39,6 +45,7 @@ {"1.2.1", [{restart_application, diameter}]}, {"1.3", [{restart_application, diameter}]}, {"1.3.1", [{restart_application, diameter}]}, - {"1.4", [{restart_application, diameter}]} + {"1.4", [{restart_application, diameter}]}, + {"1.4.1", [{restart_application, diameter}]} ] }. diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index c67fba5f89..57730cad61 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -45,6 +45,7 @@ -export_type([evaluable/0, restriction/0, + remotes/0, sequence/0, app_alias/0, service_name/0, @@ -292,13 +293,20 @@ call(SvcName, App, Message) -> | [node()] | evaluable(). +-type remotes() + :: boolean() + | [node()] + | evaluable(). + %% Options passed to start_service/2 -type service_opt() :: capability() | {application, [application_opt()]} | {restrict_connections, restriction()} - | {sequence, sequence() | evaluable()}. + | {sequence, sequence() | evaluable()} + | {share_peers, remotes()} + | {use_shared_peers, remotes()}. -type application_opt() :: {alias, app_alias()} @@ -327,7 +335,7 @@ call(SvcName, App, Message) -> -type transport_opt() :: {transport_module, atom()} | {transport_config, any()} - | {transport_config, any(), non_neg_integer() | infinity} + | {transport_config, any(), 'Unsigned32'() | infinity} | {applications, [app_alias()]} | {capabilities, [capability()]} | {capabilities_cb, evaluable()} @@ -336,6 +344,7 @@ call(SvcName, App, Message) -> | {length_errors, exit | handle | discard} | {reconnect_timer, 'Unsigned32'()} | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} + | {watchdog_config, [{okay|suspect, non_neg_integer()}]} | {private, any()}. %% Predicate passed to remove_transport/2 diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 715b15628c..4b821f5139 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -172,7 +172,50 @@ ipaddr(A) -> bCER(#diameter_caps{} = Rec, Dict) -> Values = lists:zip(Dict:'#info-'(diameter_base_CER, fields), tl(tuple_to_list(Rec))), - Dict:'#new-'(diameter_base_CER, Values). + Dict:'#new-'(diameter_base_CER, [{K, map(K, V, Dict)} + || {K,V} <- Values]). + +%% map/3 +%% +%% Deal with differerences in common dictionary AVP's to make changes +%% transparent in service/transport config. In particular, one +%% annoying difference between RFC 3588 and RFC 6733. +%% +%% RFC 6773 changes the definition of Vendor-Specific-Application-Id, +%% giving Vendor-Id arity 1 instead of 3588's 1*. This causes woe +%% since the corresponding dictionaries expect different values for a +%% 'Vendor-Id': a list for 3588, an integer for 6733. + +map('Vendor-Specific-Application-Id', L, Dict) -> + Rec = Dict:'#new-'('diameter_base_Vendor-Specific-Application-Id', []), + Def = Dict:'#get-'('Vendor-Id', Rec), + [vsa(V, Def) || V <- L]; +map(_, V, _) -> + V. + +vsa({_, N, _, _} = Rec, []) + when is_integer(N) -> + setelement(2, Rec, [N]); + +vsa({_, [N], _, _} = Rec, undefined) + when is_integer(N) -> + setelement(2, Rec, N); + +vsa([_|_] = L, Def) -> + [vid(T, Def) || T <- L]; + +vsa(T, _) -> + T. + +vid({'Vendor-Id' = K, N}, []) + when is_integer(N) -> + {K, [N]}; + +vid({'Vendor-Id' = K, [N]}, undefined) -> + {K, N}; + +vid(T, _) -> + T. %% rCER/3 %% @@ -239,9 +282,26 @@ build_CEA(_, LCaps, RCaps, Dict, CEA) -> [] -> Dict:'#set-'({'Result-Code', ?NOSECURITY}, CEA); [_] = IS -> - Dict:'#set-'({'Inband-Security-Id', IS}, CEA) + Dict:'#set-'({'Inband-Security-Id', inband_security(IS)}, CEA) end. +%% Only set Inband-Security-Id if different from the default, since +%% RFC 6733 recommends against the AVP: +%% +%% 6.10. Inband-Security-Id AVP +%% +%% The Inband-Security-Id AVP (AVP Code 299) is of type Unsigned32 and +%% is used in order to advertise support of the security portion of the +%% application. The use of this AVP in CER and CEA messages is NOT +%% RECOMMENDED. Instead, discovery of a Diameter entity's security +%% capabilities can be done either through static configuration or via +%% Diameter Peer Discovery as described in Section 5.2. + +inband_security([?NO_INBAND_SECURITY]) -> + []; +inband_security([_] = IS) -> + IS. + %% common_security/2 common_security(#diameter_caps{inband_security_id = LS}, diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index e446a0209c..6c0e73de36 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -38,6 +38,10 @@ -define(MASK(N,I), ((I) band (1 bsl (N)))). +-type u32() :: 0..16#FFFFFFFF. +-type u24() :: 0..16#FFFFFF. +-type u1() :: 0..1. + %% 0 1 2 3 %% 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 %% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -55,9 +59,13 @@ %% +-+-+-+-+-+-+-+-+-+-+-+-+- %%% --------------------------------------------------------------------------- -%%% # encode/[2-4] +%%% # encode/2 %%% --------------------------------------------------------------------------- +-spec encode(module(), Msg :: term()) + -> #diameter_packet{} + | no_return(). + encode(Mod, #diameter_packet{} = Pkt) -> try e(Mod, Pkt) @@ -217,6 +225,9 @@ rec2msg(Mod, Rec) -> %% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors. +-spec decode(module(), #diameter_packet{} | bitstring()) + -> #diameter_packet{}. + decode(Mod, Pkt) -> decode(Mod:id(), Mod, Pkt). @@ -225,9 +236,9 @@ decode(Mod, Pkt) -> %% question. decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) -> case collect_avps(Pkt) of - {Bs, As} -> + {E, As} -> Pkt#diameter_packet{avps = As, - errors = [Bs]}; + errors = [E]}; As -> Pkt#diameter_packet{avps = As} end; @@ -251,12 +262,12 @@ decode(Id, Mod, Bin) when is_bitstring(Bin) -> decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}). -decode_avps(MsgName, Mod, Pkt, {Bs, Avps}) -> %% invalid avp bits ... +decode_avps(MsgName, Mod, Pkt, {E, Avps}) -> ?LOG(invalid, Pkt#diameter_packet.bin), #diameter_packet{errors = Failed} = P = decode_avps(MsgName, Mod, Pkt, Avps), - P#diameter_packet{errors = [Bs | Failed]}; + P#diameter_packet{errors = [E | Failed]}; decode_avps('', Mod, Pkt, Avps) -> %% unknown message ... ?LOG(unknown, {Mod, Pkt#diameter_packet.header}), @@ -275,6 +286,10 @@ decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not %%% # decode_header/1 %%% --------------------------------------------------------------------------- +-spec decode_header(bitstring()) + -> #diameter_header{} + | false. + decode_header(<<Version:8, MsgLength:24, CmdFlags:1/binary, @@ -324,6 +339,13 @@ decode_header(_) -> %% wraparound counter. The 8-bit counter is incremented each time the %% system is restarted. +-spec sequence_numbers(#diameter_packet{} + | #diameter_header{} + | binary() + | Seq) + -> Seq + when Seq :: {HopByHopId :: u32(), EndToEndId :: u32()}. + sequence_numbers({_,_} = T) -> T; @@ -345,6 +367,9 @@ sequence_numbers(<<_:12/binary, H:32, E:32, _/binary>>) -> %%% # hop_by_hop_id/2 %%% --------------------------------------------------------------------------- +-spec hop_by_hop_id(u32(), binary()) + -> binary(). + hop_by_hop_id(Id, <<H:12/binary, _:32, T/binary>>) -> <<H/binary, Id:32, T/binary>>. @@ -352,6 +377,10 @@ hop_by_hop_id(Id, <<H:12/binary, _:32, T/binary>>) -> %%% # msg_name/2 %%% --------------------------------------------------------------------------- +-spec msg_name(module(), #diameter_header{}) + -> atom() + | {ApplicationId :: u32(), CommandCode :: u24(), Rbit :: u1()}. + msg_name(Dict0, #diameter_header{application_id = ?APP_ID_COMMON, cmd_code = C, is_request = R}) -> @@ -367,6 +396,9 @@ msg_name(_, Hdr) -> %%% # msg_id/1 %%% --------------------------------------------------------------------------- +-spec msg_id(#diameter_packet{} | #diameter_header{}) + -> {ApplicationId :: u32(), CommandCode :: u24(), Rbit :: u1()}. + msg_id(#diameter_packet{msg = [#diameter_header{} = Hdr | _]}) -> msg_id(Hdr); @@ -389,6 +421,12 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) -> %% order in the binary. Note also that grouped avp's aren't unraveled, %% only those at the top level. +-spec collect_avps(#diameter_packet{} | bitstring()) + -> [Avp] + | {Error, [Avp]} + when Avp :: #diameter_avp{}, + Error :: {5014, #diameter_avp{}}. + collect_avps(#diameter_packet{bin = Bin}) -> <<_:20/binary, Avps/bitstring>> = Bin, collect_avps(Avps); @@ -403,8 +441,8 @@ collect_avps(Bin, N, Acc) -> {Rest, AVP} -> collect_avps(Rest, N+1, [AVP#diameter_avp{index = N} | Acc]) catch - ?FAILURE(_) -> - {Bin, Acc} + ?FAILURE(Error) -> + {Error, Acc} end. %% 0 1 2 3 @@ -422,42 +460,87 @@ collect_avps(Bin, N, Acc) -> %% split_avp/1 split_avp(Bin) -> - 8 =< size(Bin) orelse ?THROW(truncated_header), + {Code, V, M, P, Len, HdrLen} = split_head(Bin), + {Data, B} = split_data(Bin, HdrLen, Len - HdrLen), - <<Code:32, Flags:1/binary, Length:24, Rest/bitstring>> - = Bin, + {B, #diameter_avp{code = Code, + vendor_id = V, + is_mandatory = 1 == M, + need_encryption = 1 == P, + data = Data}}. - DataSize = Length - 8, % size(Code+Flags+Length) = 8 octets - PadSize = (4 - (DataSize rem 4)) rem 4, +%% split_head/1 - DataSize + PadSize =< size(Rest) - orelse ?THROW(truncated_data), +split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/bitstring>>) -> + {Code, V, M, P, Len, 12}; - <<Data:DataSize/binary, _:PadSize/binary, R/bitstring>> - = Rest, - <<Vbit:1, Mbit:1, Pbit:1, _Reserved:5>> - = Flags, +split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/bitstring>>) -> + {Code, undefined, M, P, Len, 8}; - 0 == Vbit orelse 4 =< size(Data) - orelse ?THROW(truncated_vendor_id), +split_head(Bin) -> + ?THROW({5014, #diameter_avp{data = Bin}}). + +%% 3588: +%% +%% DIAMETER_INVALID_AVP_LENGTH 5014 +%% The request contained an AVP with an invalid length. A Diameter +%% message indicating this error MUST include the offending AVPs +%% within a Failed-AVP AVP. - {Vid, D} = vid(Vbit, Data), - {R, #diameter_avp{code = Code, - vendor_id = Vid, - is_mandatory = 1 == Mbit, - need_encryption = 1 == Pbit, - data = D}}. +%% 6733: +%% +%% DIAMETER_INVALID_AVP_LENGTH 5014 +%% +%% The request contained an AVP with an invalid length. A Diameter +%% message indicating this error MUST include the offending AVPs +%% within a Failed-AVP AVP. In cases where the erroneous AVP length +%% value exceeds the message length or is less than the minimum AVP +%% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +%% header length, it is sufficient to include the offending AVP +%% ^^^^^^^^^^^^^ +%% header and a zero filled payload of the minimum required length +%% for the payloads data type. If the AVP is a Grouped AVP, the +%% Grouped AVP header with an empty payload would be sufficient to +%% indicate the offending AVP. In the case where the offending AVP +%% header cannot be fully decoded when the AVP length is less than +%% the minimum AVP header length, it is sufficient to include an +%% offending AVP header that is formulated by padding the incomplete +%% AVP header with zero up to the minimum AVP header length. +%% +%% The underlined clause must be in error since (1) a header less than +%% the minimum value mean we don't know the identity of the AVP and +%% (2) the last sentence covers this case. -%% The RFC is a little misleading when stating that OctetString is -%% padded to a 32-bit boundary while other types align naturally. All -%% other types are already multiples of 32 bits so there's no need to -%% distinguish between types here. Any invalid lengths will result in -%% decode error in diameter_types. +%% split_data/3 -vid(1, <<Vid:32, Data/bitstring>>) -> - {Vid, Data}; -vid(0, Data) -> - {undefined, Data}. +split_data(Bin, HdrLen, Len) + when 0 =< Len -> + split_data(Bin, HdrLen, Len, (4 - (Len rem 4)) rem 4); + +split_data(_, _, _) -> + invalid_avp_length(). + +%% split_data/4 + +split_data(Bin, HdrLen, Len, Pad) -> + <<_:HdrLen/binary, T/bitstring>> = Bin, + case T of + <<Data:Len/binary, _:Pad/binary, Rest/bitstring>> -> + {Data, Rest}; + _ -> + invalid_avp_length() + end. + +%% invalid_avp_length/0 +%% +%% AVP Length doesn't mesh with payload. Induce a decode error by +%% returning a payload that no valid Diameter type can have. This is +%% so that a known AVP will result in 5014 error with a zero'd +%% payload. Here we simply don't know how to construct this payload. +%% (Yes, this solution is an afterthought.) + +invalid_avp_length() -> + {<<0:1>>, <<>>}. %%% --------------------------------------------------------------------------- %%% # pack_avp/1 @@ -472,20 +555,35 @@ vid(0, Data) -> pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) -> pack_avp(A#diameter_avp{data = encode_avps(Avps)}); -%% ... data as a type/value tuple, possibly with header data, ... +%% ... data as a type/value tuple ... pack_avp(#diameter_avp{data = {Type, Value}} = A) when is_atom(Type) -> pack_avp(A#diameter_avp{data = diameter_types:Type(encode, Value)}); + +%% ... with a header in various forms ... pack_avp(#diameter_avp{data = {{_,_,_} = T, {Type, Value}}}) -> pack_avp(T, iolist_to_binary(diameter_types:Type(encode, Value))); + pack_avp(#diameter_avp{data = {{_,_,_} = T, Bin}}) when is_binary(Bin) -> pack_avp(T, Bin); + pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) -> {Code, _Flags, Vid} = Hdr = Dict:avp_header(Name), {Name, Type} = Dict:avp_name(Code, Vid), pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}}); +pack_avp(#diameter_avp{code = undefined, data = Bin}) + when is_binary(Bin) -> + %% Reset the AVP Length of an AVP Header resulting from a 5014 + %% error. The RFC doesn't explicitly say to do this but the + %% receiver can't correctly extract this and following AVP's + %% without a correct length. On the downside, the header doesn't + %% reveal if the received header has been padded. + Pad = 8*header_length(Bin) - bit_size(Bin), + Len = size(<<H:5/binary, _:24, T/binary>> = <<Bin/bitstring, 0:Pad>>), + <<H/binary, Len:24, T/binary>>; + %% ... or as an iolist. pack_avp(#diameter_avp{code = Code, vendor_id = V, @@ -497,6 +595,11 @@ pack_avp(#diameter_avp{code = Code, {P, 2#00100000}]), pack_avp({Code, Flags, V}, iolist_to_binary(Data)). +header_length(<<_:32, 1:1, _/bitstring>>) -> + 12; +header_length(_) -> + 8. + flag_avp({true, B}, F) -> F bor B; flag_avp({false, _}, F) -> diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 9f73815756..2a145c874b 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -103,6 +103,10 @@ %% Time to lay low before restarting a dead service. -define(RESTART_SLEEP, 2000). +%% Test for a valid timeout. +-define(IS_UINT32(N), + is_integer(N) andalso 0 =< N andalso 0 == N bsr 32). + %% A minimal diameter_caps for checking for valid capabilities values. -define(EXAMPLE_CAPS, #diameter_caps{origin_host = "TheHost", @@ -490,13 +494,11 @@ stop(SvcName) -> %% has many. add(SvcName, Type, Opts) -> - %% Ensure usable capabilities. diameter_service:merge_service/2 - %% depends on this. - lists:foreach(fun(Os) -> - is_list(Os) orelse ?THROW({capabilities, Os}), - ok = encode_CER(Os) - end, - [Os || {capabilities, Os} <- Opts, is_list(Os)]), + %% Ensure acceptable transport options. This won't catch all + %% possible errors (faulty callbacks for example) but it catches + %% many. diameter_service:merge_service/2 depends on usable + %% capabilities for example. + ok = transport_opts(Opts), Ref = make_ref(), T = {Ref, Type, Opts}, @@ -514,6 +516,61 @@ add(SvcName, Type, Opts) -> No end. +transport_opts(Opts) -> + lists:foreach(fun(T) -> opt(T) orelse ?THROW({invalid, T}) end, Opts). + +opt({transport_module, M}) -> + is_atom(M); + +opt({transport_config, _, Tmo}) -> + ?IS_UINT32(Tmo) orelse Tmo == infinity; + +opt({applications, As}) -> + is_list(As); + +opt({capabilities, Os}) -> + is_list(Os) andalso ok == encode_CER(Os); + +opt({capx_timeout, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({length_errors, T}) -> + lists:member(T, [exit, handle, discard]); + +opt({reconnect_timer, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({watchdog_timer, {M,F,A}}) + when is_atom(M), is_atom(F), is_list(A) -> + true; +opt({watchdog_timer, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({watchdog_config, L}) -> + is_list(L) andalso lists:all(fun wdopt/1, L); + +%% Options that we can't validate. +opt({K, _}) + when K == transport_config; + K == capabilities_cb; + K == disconnect_cb; + K == private -> + true; + +%% Anything else, which is ignored by us. This makes options sensitive +%% to spelling mistakes but arbitrary options are passed by some users +%% as a way to identify transports. (That is, can't just do away with +%% it.) +opt(_) -> + true. + +wdopt({K,N}) -> + (K == okay orelse K == suspect) andalso is_integer(N) andalso 0 =< N; +wdopt(_) -> + false. + +%% start_transport/2 + start_transport(SvcName, T) -> case diameter_service:start_transport(SvcName, T) of {ok, _Pid} -> @@ -557,54 +614,69 @@ stop_transport(SvcName, Refs) -> %% make_config/2 make_config(SvcName, Opts) -> - Apps = init_apps(Opts), + AppOpts = [T || {application, _} = T <- Opts], + Apps = init_apps(AppOpts), + [] == Apps andalso ?THROW(no_apps), %% Use the fact that diameter_caps has the same field names as CER. Fields = ?BASE:'#info-'(diameter_base_CER) -- ['AVP'], - COpts = [T || {K,_} = T <- Opts, lists:member(K, Fields)], - Caps = make_caps(#diameter_caps{}, COpts), + CapOpts = [T || {K,_} = T <- Opts, lists:member(K, Fields)], + Caps = make_caps(#diameter_caps{}, CapOpts), - ok = encode_CER(COpts), + ok = encode_CER(CapOpts), - Os = split(Opts, fun opt/2, [{false, share_peers}, - {false, use_shared_peers}, - {false, monitor}, - {?NOMASK, sequence}, - {nodes, restrict_connections}]), - %% share_peers and use_shared_peers are currently undocumented. + SvcOpts = make_opts((Opts -- AppOpts) -- CapOpts, + [{false, share_peers}, + {false, use_shared_peers}, + {false, monitor}, + {?NOMASK, sequence}, + {nodes, restrict_connections}]), #service{name = SvcName, rec = #diameter_service{applications = Apps, capabilities = Caps}, - options = Os}. + options = SvcOpts}. + +make_opts(Opts, Defs) -> + Known = [{K, get_opt(K, Opts, D)} || {D,K} <- Defs], + Unknown = Opts -- Known, -split(Opts, F, Defs) -> - [{K, F(K, get_opt(K, Opts, D))} || {D,K} <- Defs]. + [] == Unknown orelse ?THROW({invalid, hd(Unknown)}), + + [{K, opt(K,V)} || {K,V} <- Known]. opt(K, false = B) when K /= sequence -> B; opt(K, true = B) - when K == share_peer; + when K == share_peers; K == use_shared_peers -> B; -opt(monitor, P) - when is_pid(P) -> - P; - opt(restrict_connections, T) when T == node; - T == nodes; - T == []; - is_atom(hd(T)) -> + T == nodes -> + T; + +opt(K, T) + when (K == share_peers + orelse K == use_shared_peers + orelse K == restrict_connections), ([] == T + orelse is_atom(hd(T))) -> T; -opt(restrict_connections = K, F) -> - try diameter_lib:eval(F) of %% no guarantee that it won't fail later +opt(monitor, P) + when is_pid(P) -> + P; + +opt(K, F) + when K == restrict_connections; + K == share_peers; + K == use_shared_peers -> + try diameter_lib:eval(F) of %% but no guarantee that it won't fail later Nodes when is_list(Nodes) -> F; V -> @@ -629,7 +701,7 @@ opt(K, _) -> ?THROW({value, K}). sequence({H,N} = T) - when 0 =< N, N =< 32, 0 =< H, 0 == H bsr N -> + when 0 =< N, N =< 32, 0 =< H, 0 == H bsr (32-N) -> T; sequence(_) -> @@ -664,8 +736,8 @@ encode_CER(Opts) -> init_apps(Opts) -> lists:foldl(fun app_acc/2, [], lists:reverse(Opts)). -app_acc({application, Opts}, Acc) -> - is_list(Opts) orelse ?THROW({application, Opts}), +app_acc({application, Opts} = T, Acc) -> + is_list(Opts) orelse ?THROW(T), [Dict, Mod] = get_opt([dictionary, module], Opts), Alias = get_opt(alias, Opts, Dict), @@ -681,9 +753,7 @@ app_acc({application, Opts}, Acc) -> mutable = M, options = [{answer_errors, A}, {request_errors, P}]} - | Acc]; -app_acc(_, Acc) -> - Acc. + | Acc]. init_mod(#diameter_callback{} = R) -> init_mod([diameter_callback, R]); diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl index 362d593b24..44d81e2778 100644 --- a/lib/diameter/src/base/diameter_lib.erl +++ b/lib/diameter/src/base/diameter_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -19,77 +19,86 @@ -module(diameter_lib). --export([report/2, info_report/2, +-export([info_report/2, error_report/2, warning_report/2, now_diff/1, time/1, eval/1, - ip4address/1, - ip6address/1, ipaddr/1, spawn_opts/2, wait/1, fold_tuple/3, log/4]). --include("diameter_internal.hrl"). - %% --------------------------------------------------------------------------- -%% # info_report(Reason, MFA) -%% -%% Input: Reason = Arbitrary term indicating the reason for the report. -%% MFA = {Module, Function, Args} to report. -%% -%% Output: true +%% # info_report/2 %% --------------------------------------------------------------------------- -report(Reason, MFA) -> - info_report(Reason, MFA). +-spec info_report(Reason :: term(), T :: term()) + -> true. -info_report(Reason, MFA) -> - report(fun error_logger:info_report/1, Reason, MFA), +info_report(Reason, T) -> + report(fun error_logger:info_report/1, Reason, T), true. -%%% --------------------------------------------------------------------------- -%%% # error_report(Reason, MFA) -%%% # warning_report(Reason, MFA) -%%% -%%% Output: false -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # error_report/2 +%% # warning_report/2 +%% --------------------------------------------------------------------------- + +-spec error_report(Reason :: term(), T :: term()) + -> false. + +error_report(Reason, T) -> + report(fun error_logger:error_report/1, Reason, T). -error_report(Reason, MFA) -> - report(fun error_logger:error_report/1, Reason, MFA). +-spec warning_report(Reason :: term(), T :: term()) + -> false. -warning_report(Reason, MFA) -> - report(fun error_logger:warning_report/1, Reason, MFA). +warning_report(Reason, T) -> + report(fun error_logger:warning_report/1, Reason, T). -report(Fun, Reason, MFA) -> - Fun([{why, Reason}, {who, self()}, {what, MFA}]), +report(Fun, Reason, T) -> + Fun([{why, Reason}, {who, self()}, {what, T}]), false. -%%% --------------------------------------------------------------------------- -%%% # now_diff(Time) -%%% -%%% Description: Return timer:now_diff(now(), Time) as an {H, M, S, MicroS} -%%% tuple instead of as integer microseconds. -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # now_diff/1 +%% --------------------------------------------------------------------------- + +-spec now_diff(NowT) + -> {Hours, Mins, Secs, MicroSecs} + when NowT :: {non_neg_integer(), 0..999999, 0..999999}, + Hours :: non_neg_integer(), + Mins :: 0..59, + Secs :: 0..59, + MicroSecs :: 0..999999. + +%% Return timer:now_diff(now(), NowT) as an {H, M, S, MicroS} tuple +%% instead of as integer microseconds. now_diff({_,_,_} = Time) -> - time(timer:now_diff(erlang:now(), Time)). - -%%% --------------------------------------------------------------------------- -%%% # time(Time) -%%% -%%% Input: Time = {MegaSec, Sec, MicroSec} -%%% | MicroSec -%%% -%%% Output: {H, M, S, MicroS} -%%% --------------------------------------------------------------------------- - -time({_,_,_} = Time) -> %% time of day + time(timer:now_diff(now(), Time)). + +%% --------------------------------------------------------------------------- +%% # time/1 +%% +%% Return an elapsed time as an {H, M, S, MicroS} tuple. +%% --------------------------------------------------------------------------- + +-spec time(NowT | Diff) + -> {Hours, Mins, Secs, MicroSecs} + when NowT :: {non_neg_integer(), 0..999999, 0..999999}, + Diff :: non_neg_integer(), + Hours :: non_neg_integer(), + Mins :: 0..59, + Secs :: 0..59, + MicroSecs :: 0..999999. + +time({_,_,_} = NowT) -> %% time of day %% 24 hours = 24*60*60*1000000 = 86400000000 microsec - time(timer:now_diff(Time, {0,0,0}) rem 86400000000); + time(timer:now_diff(NowT, {0,0,0}) rem 86400000000); time(Micro) -> %% elapsed time Seconds = Micro div 1000000, @@ -98,9 +107,21 @@ time(Micro) -> %% elapsed time S = Seconds rem 60, {H, M, S, Micro rem 1000000}. -%%% --------------------------------------------------------------------------- -%%% # eval(Func) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # eval/1 +%% +%% Evaluate a function in various forms. +%% --------------------------------------------------------------------------- + +-type f() :: {module(), atom(), list()} + | nonempty_maybe_improper_list(fun(), list()) + | fun(). + +-spec eval(Fun) + -> term() + when Fun :: f() + | {f()} + | nonempty_maybe_improper_list(f(), list()). eval({M,F,A}) -> apply(M,F,A); @@ -120,66 +141,15 @@ eval({F}) -> eval(F) -> F(). -%%% --------------------------------------------------------------------------- -%%% # ip4address(Addr) -%%% -%%% Input: string() (eg. "10.0.0.1") -%%% | list of integer() -%%% | tuple of integer() -%%% -%%% Output: {_,_,_,_} of integer -%%% -%%% Exceptions: error: {invalid_address, Addr, erlang:get_stacktrace()} -%%% --------------------------------------------------------------------------- - -ip4address([_,_,_,_] = Addr) -> %% Length 4 string can't be an address. - ipaddr(list_to_tuple(Addr)); - -%% Be brutal. -ip4address(Addr) -> - try - {_,_,_,_} = ipaddr(Addr) - catch - error: _ -> - erlang:error({invalid_address, Addr, ?STACK}) - end. - -%%% --------------------------------------------------------------------------- -%%% # ip6address(Addr) -%%% -%%% Input: string() (eg. "1080::8:800:200C:417A") -%%% | list of integer() -%%% | tuple of integer() -%%% -%%% Output: {_,_,_,_,_,_,_,_} of integer -%%% -%%% Exceptions: error: {invalid_address, Addr, erlang:get_stacktrace()} -%%% --------------------------------------------------------------------------- - -ip6address([_,_,_,_,_,_,_,_] = Addr) -> %% Length 8 string can't be an address. - ipaddr(list_to_tuple(Addr)); - -%% Be brutal. -ip6address(Addr) -> - try - {_,_,_,_,_,_,_,_} = ipaddr(Addr) - catch - error: _ -> - erlang:error({invalid_address, Addr, ?STACK}) - end. - -%%% --------------------------------------------------------------------------- -%%% # ipaddr(Addr) -%%% -%%% Input: string() | tuple of integer() -%%% -%%% Output: {_,_,_,_} | {_,_,_,_,_,_,_,_} -%%% -%%% Exceptions: error: {invalid_address, erlang:get_stacktrace()} -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # ipaddr/1 +%% +%% Parse an IP address. +%% --------------------------------------------------------------------------- --spec ipaddr(string() | tuple()) - -> inet:ip_address(). +-spec ipaddr([byte()] | tuple()) + -> inet:ip_address() + | none(). %% Don't convert lists of integers since a length 8 list like %% [$1,$0,$.,$0,$.,$0,$.,$1] is ambiguous: is it "10.0.0.1" or @@ -193,7 +163,7 @@ ipaddr(Addr) -> ip(Addr) catch error: _ -> - erlang:error({invalid_address, ?STACK}) + erlang:error({invalid_address, erlang:get_stacktrace()}) end. %% Already a tuple: ensure non-negative integers of the right size. @@ -210,11 +180,12 @@ ip(Addr) -> {ok, A} = inet_parse:address(Addr), %% documented in inet(3) A. -%%% --------------------------------------------------------------------------- -%%% # spawn_opts(Type, Opts) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # spawn_opts/2 +%% --------------------------------------------------------------------------- -%% TODO: config variables. +-spec spawn_opts(server|worker, list()) + -> list(). spawn_opts(server, Opts) -> opts(75000, Opts); @@ -224,24 +195,32 @@ spawn_opts(worker, Opts) -> opts(HeapSize, Opts) -> [{min_heap_size, HeapSize} | lists:keydelete(min_heap_size, 1, Opts)]. -%%% --------------------------------------------------------------------------- -%%% # wait(MRefs) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # wait/1 +%% --------------------------------------------------------------------------- + +-spec wait([pid()]) + -> ok. wait(L) -> - w([erlang:monitor(process, P) || P <- L]). + down([erlang:monitor(process, P) || P <- L]). -w([]) -> +down([]) -> ok; -w(L) -> - receive - {'DOWN', MRef, process, _, _} -> - w(lists:delete(MRef, L)) - end. +down([MRef|T]) -> + receive {'DOWN', MRef, process, _, _} -> ok end, + down(T). -%%% --------------------------------------------------------------------------- -%%% # fold_tuple(N, T0, T) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # fold_tuple/3 +%% --------------------------------------------------------------------------- + +-spec fold_tuple(N, T0, T) + -> tuple() + when N :: pos_integer(), + T0 :: tuple(), + T :: tuple() + | undefined. %% Replace fields in T0 by those of T starting at index N, unless the %% new value is 'undefined'. @@ -262,11 +241,11 @@ ft(undefined, {_, T}) -> ft(Value, {Idx, T}) -> setelement(Idx, T, Value). -%%% ---------------------------------------------------------- -%%% # log(Slogan, Mod, Line, Details) -%%% -%%% Called to have something to trace on for happenings of interest. -%%% ---------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # log/4 +%% +%% Called to have something to trace on for happenings of interest. +%% --------------------------------------------------------------------------- -log(_, _, _, _) -> +log(_Slogan, _Mod, _Line, _Details) -> ok. diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl index 130bedda84..0d2efd4d1f 100644 --- a/lib/diameter/src/base/diameter_peer.erl +++ b/lib/diameter/src/base/diameter_peer.erl @@ -24,14 +24,15 @@ %% Interface towards transport modules ... -export([recv/2, up/1, - up/2]). + up/2, + up/3]). %% ... and the stack. -export([start/1, send/2, close/1, abort/1, - notify/2]). + notify/3]). %% Server start. -export([start_link/0]). @@ -63,11 +64,11 @@ -define(DEFAULT_TTMO, infinity). %%% --------------------------------------------------------------------------- -%%% # notify/2 +%%% # notify/3 %%% --------------------------------------------------------------------------- -notify(SvcName, T) -> - rpc:abcast(nodes(), ?SERVER, {notify, SvcName, T}). +notify(Nodes, SvcName, T) -> + rpc:abcast(Nodes, ?SERVER, {notify, SvcName, T}). %%% --------------------------------------------------------------------------- %%% # start/1 @@ -180,7 +181,7 @@ start(Mod, Args) -> apply(Mod, start, Args). %%% --------------------------------------------------------------------------- -%%% # up/[12] +%%% # up/1-3 %%% --------------------------------------------------------------------------- up(Pid) -> %% accepting transport @@ -189,6 +190,9 @@ up(Pid) -> %% accepting transport up(Pid, Remote) -> %% connecting transport ifc_send(Pid, {self(), connected, Remote}). +up(Pid, Remote, LAddrs) -> %% connecting transport + ifc_send(Pid, {self(), connected, Remote, LAddrs}). + %%% --------------------------------------------------------------------------- %%% # recv/2 %%% --------------------------------------------------------------------------- diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 66342f7b62..4e55864168 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -198,6 +198,7 @@ i({Ack, WPid, {M, Ref} = T, Opts, {Mask, OnLengthErr = proplists:get_value(length_errors, Opts, exit), lists:member(OnLengthErr, [exit, handle, discard]) orelse ?ERROR({invalid, {length_errors, OnLengthErr}}), + %% Error checking is for configuration added in old code. {TPid, Addrs} = start_transport(T, Rest, Svc), @@ -212,9 +213,6 @@ i({Ack, WPid, {M, Ref} = T, Opts, {Mask, %% transports on the same service can use different local addresses. %% The local addresses are put into Host-IP-Address avps here when %% sending capabilities exchange messages. -%% -%% Invalid transport config may cause us to crash but note that the -%% watchdog start (start/2) succeeds regardless. %% Wait for the caller to have a monitor to avoid a race with our %% death. (Since the exit reason is used in diameter_service.) @@ -235,20 +233,21 @@ start_transport(Addrs0, T) -> {TPid, Addrs, Tmo, Data} -> erlang:monitor(process, TPid), q_next(TPid, Addrs0, Tmo, Data), - {TPid, addrs(Addrs, Addrs0)}; + {TPid, Addrs}; No -> exit({shutdown, No}) end. -addrs([], Addrs0) -> - Addrs0; -addrs(Addrs, _) -> - Addrs. - -svc(Svc, []) -> - Svc; -svc(Svc, Addrs) -> - readdr(Svc, Addrs). +svc(#diameter_service{capabilities = LCaps0} = Svc, Addrs) -> + #diameter_caps{host_ip_address = Addrs0} + = LCaps0, + case Addrs0 of + [] -> + LCaps = LCaps0#diameter_caps{host_ip_address = Addrs}, + Svc#diameter_service{capabilities = LCaps}; + [_|_] -> + Svc + end. readdr(#diameter_service{capabilities = LCaps0} = Svc, Addrs) -> LCaps = LCaps0#diameter_caps{host_ip_address = Addrs}, @@ -353,10 +352,17 @@ transition({diameter, {TPid, connected, Remote}}, mode = M} = S) -> {'Wait-Conn-Ack', _} = PS, %% assert - connect = M, %% + connect = M, %% keep_transport(TPid), send_CER(S#state{mode = {M, Remote}}); +transition({diameter, {TPid, connected, Remote, LAddrs}}, + #state{transport = TPid, + service = Svc} + = S) -> + transition({diameter, {TPid, connected, Remote}}, + S#state{service = svc(Svc, LAddrs)}); + %% Connection from peer. transition({diameter, {TPid, connected}}, #state{transport = TPid, @@ -365,7 +371,7 @@ transition({diameter, {TPid, connected}}, parent = Pid} = S) -> {'Wait-Conn-Ack', Tmo} = PS, %% assert - accept = M, %% + accept = M, %% keep_transport(TPid), Pid ! {accepted, self()}, start_timer(Tmo, S#state{state = recv_CER}); @@ -378,6 +384,8 @@ transition({diameter, {_, connected}}, _) -> {stop, connection_timeout}; transition({diameter, {_, connected, _}}, _) -> {stop, connection_timeout}; +transition({diameter, {_, connected, _, _}}, _) -> + {stop, connection_timeout}; %% Connection has timed out: start an alternate. transition({connection_timeout = T, TPid}, @@ -695,13 +703,13 @@ build_answer('CER', N -> {cea(CEA, N, Dict0), [fun open/5, Pkt, SupportedApps, Caps, - {accept, hd([_] = IS)}]} + {accept, inband_security(IS)}]} catch ?FAILURE(Reason) -> rejected(Reason, {'CER', Reason, Caps, Pkt}, S) end; -%% The error checks below are similar to those in diameter_service for +%% The error checks below are similar to those in diameter_traffic for %% other messages. Should factor out the commonality. build_answer(Type, @@ -712,6 +720,11 @@ build_answer(Type, RC = rc(H, Es), {answer(Type, RC, Es, S), post(Type, RC, Pkt, S)}. +inband_security([]) -> + ?NO_INBAND_SECURITY; +inband_security([IS]) -> + IS. + cea(CEA, ok, _) -> CEA; cea(CEA, 2001, _) -> @@ -735,7 +748,14 @@ rejected(N, T, S) -> rejected({N, []}, T, S). answer(Type, RC, Es, S) -> - set(answer(Type, RC, S), failed_avp([A || {_,A} <- Es])). + set(answer(Type, RC, S), failed_avp(RC, Es)). + +failed_avp(RC, [{RC, Avp} | _]) -> + [{'Failed-AVP', [{'AVP', [Avp]}]}]; +failed_avp(RC, [_ | Es]) -> + failed_avp(RC, Es); +failed_avp(_, [] = No) -> + No. answer(Type, RC, S) -> answer_message(answer(Type, S), RC). @@ -755,13 +775,6 @@ is_origin({N, _}) -> orelse N == 'Origin-Realm' orelse N == 'Origin-State-Id'. -%% failed_avp/1 - -failed_avp([] = No) -> - No; -failed_avp(Avps) -> - [{'Failed-AVP', [[{'AVP', Avps}]]}]. - %% set/2 set(Ans, []) -> @@ -777,7 +790,7 @@ rc(#diameter_header{is_error = true}, _) -> 3008; %% DIAMETER_INVALID_HDR_BITS rc(_, [Bs|_]) - when is_bitstring(Bs) -> + when is_bitstring(Bs) -> %% from old code 3009; %% DIAMETER_INVALID_HDR_BITS rc(#diameter_header{version = ?DIAMETER_VERSION}, Es) -> @@ -846,8 +859,12 @@ a('DPR', #diameter_caps{origin_host = {Host, _}, %% recv_CER/2 recv_CER(CER, #state{service = Svc, dictionary = Dict}) -> - {ok, T} = diameter_capx:recv_CER(CER, Svc, Dict), - T. + case diameter_capx:recv_CER(CER, Svc, Dict) of + {ok, T} -> + T; + {error, Reason} -> + close({'CER', CER, Svc, Dict, Reason}) + end. %% handle_CEA/1 @@ -907,8 +924,12 @@ recv_CEA(#diameter_packet{header = #diameter_header{version errors = []}, #state{service = Svc, dictionary = Dict}) -> - {ok, T} = diameter_capx:recv_CEA(CEA, Svc, Dict), - T; + case diameter_capx:recv_CEA(CEA, Svc, Dict) of + {ok, T} -> + T; + {error, Reason} -> + close({'CEA', CEA, Svc, Dict, Reason}) + end; recv_CEA(Pkt, S) -> close({'CEA', caps(S), Pkt}). @@ -987,8 +1008,17 @@ capz(#diameter_caps{} = L, #diameter_caps{} = R) -> %% close/1 close(Reason) -> + report(Reason), throw({?MODULE, close, Reason}). +%% Could possibly log more here. +report({M, _, _, _, _} = T) + when M == 'CER'; + M == 'CEA' -> + diameter_lib:error_report(failure, T); +report(_) -> + ok. + %% dwa/1 dwa(#diameter_caps{origin_host = OH, diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl index ac58e4bf5b..3197c1aee1 100644 --- a/lib/diameter/src/base/diameter_reg.erl +++ b/lib/diameter/src/base/diameter_reg.erl @@ -138,7 +138,7 @@ del(T) -> %% associations removed.) %% =========================================================================== --spec match(tuple()) +-spec match(any()) -> [{term(), pid()}]. match(Pat) -> diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index f1342df16c..112e83476d 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -125,9 +125,9 @@ monitor = false :: false | pid(), %% process to die with options :: [{sequence, diameter:sequence()} %% sequence mask - | {restrict_connections, diameter:restriction()} - | {share_peers, boolean()} %% broadcast peers to remote nodes? - | {use_shared_peers, boolean()}]}).%% use broadcasted peers? + | {share_peers, diameter:remotes()} %% broadcast to + | {use_shared_peers, diameter:remotes()} %% use from + | {restrict_connections, diameter:restriction()}]}). %% shared_peers reflects the peers broadcast from remote nodes. %% Record representing an RFC 3539 watchdog process implemented by @@ -681,11 +681,9 @@ mref(false = No) -> mref(P) -> erlang:monitor(process, P). -init_shared(#state{options = [_, _, {_, true} | _], +init_shared(#state{options = [_, _, {_,T} | _], service_name = Svc}) -> - diameter_peer:notify(Svc, {service, self()}); -init_shared(#state{options = [_, _, {_, false} | _]}) -> - ok. + notify(T, Svc, {service, self()}). init_mod(#diameter_app{alias = Alias, init_state = S}) -> @@ -698,6 +696,37 @@ get_value(Key, Vs) -> {_, V} = lists:keyfind(Key, 1, Vs), V. +notify(Share, SvcName, T) -> + Nodes = remotes(Share), + [] /= Nodes andalso diameter_peer:notify(Nodes, SvcName, T). +%% Test for the empty list for upgrade reasons: there's no +%% diameter_peer:notify/3 in old code so no call means no load order +%% requirement. + +remotes(false) -> + []; + +remotes(true) -> + nodes(); + +remotes(Nodes) + when is_atom(hd(Nodes)); + Nodes == [] -> + Nodes; + +remotes(F) -> + try diameter_lib:eval(F) of + L when is_list(L) -> + L; + T -> + diameter_lib:error_report({invalid_return, T}, F), + [] + catch + E:R -> + diameter_lib:error_report({failure, {E, R, ?STACK}}, F), + [] + end. + %% --------------------------------------------------------------------------- %% # start/3 %% --------------------------------------------------------------------------- @@ -832,17 +861,21 @@ watchdog(TPid, [], ?WD_SUSPECT, ?WD_OKAY, Wd, State) -> %% Watchdog has an unresponsive connection. watchdog(TPid, [], ?WD_OKAY, ?WD_SUSPECT = To, Wd, State) -> #watchdog{peer = TPid} = Wd, %% assert - connection_down(Wd, To, State); + watchdog_down(Wd, To, State); %% Watchdog has lost its connection. watchdog(TPid, [], _, ?WD_DOWN = To, Wd, #state{peerT = PeerT} = S) -> close(Wd, S), - connection_down(Wd, To, S), + watchdog_down(Wd, To, S), ets:delete(PeerT, TPid); watchdog(_, [], _, _, _, _) -> ok. +watchdog_down(Wd, To, #state{watchdogT = WatchdogT} = S) -> + insert(WatchdogT, Wd#watchdog{state = To}), + connection_down(Wd, To, S). + %% --------------------------------------------------------------------------- %% # connection_up/3 %% --------------------------------------------------------------------------- @@ -1000,21 +1033,17 @@ connection_down(#watchdog{state = ?WD_OKAY, remove_local_peer(SApps, {{TPid, Caps}, {SvcName, Apps}}, LDict), diameter_traffic:peer_down(TPid); -connection_down(#watchdog{}, #peer{}, _) -> - ok; - -connection_down(#watchdog{state = WS, +connection_down(#watchdog{state = ?WD_OKAY, peer = TPid} = Wd, To, - #state{watchdogT = WatchdogT, - peerT = PeerT} + #state{peerT = PeerT} = S) when is_atom(To) -> - insert(WatchdogT, Wd#watchdog{state = To}), - ?WD_OKAY == WS - andalso - connection_down(Wd, fetch(PeerT, TPid), S). + connection_down(Wd, #peer{} = fetch(PeerT, TPid), S); + +connection_down(#watchdog{}, _, _) -> + ok. remove_local_peer(SApps, T, LDict) -> lists:foldl(fun(A,D) -> rlp(A, T, D) end, LDict, SApps). @@ -1233,12 +1262,12 @@ report_status(Status, peer = TPid, type = Type, options = Opts}, - #peer{apps = [_|_] = As, + #peer{apps = [_|_] = Apps, caps = Caps}, #state{service_name = SvcName} = S, Extra) -> - share_peer(Status, Caps, As, TPid, S), + share_peer(Status, Caps, Apps, TPid, S), Info = [Status, Ref, {TPid, Caps}, {type(Type), Opts} | Extra], send_event(SvcName, list_to_tuple(Info)). @@ -1255,9 +1284,9 @@ send_event(#diameter_event{service = SvcName} = E) -> %% # share_peer/5 %% --------------------------------------------------------------------------- -share_peer(up, Caps, Aliases, TPid, #state{options = [_, {_, true} | _], - service_name = Svc}) -> - diameter_peer:notify(Svc, {peer, TPid, Aliases, Caps}); +share_peer(up, Caps, Apps, TPid, #state{options = [_, {_,T} | _], + service_name = Svc}) -> + notify(T, Svc, {peer, TPid, [A || {_,A} <- Apps], Caps}); share_peer(_, _, _, _, _) -> ok. @@ -1266,34 +1295,34 @@ share_peer(_, _, _, _, _) -> %% # share_peers/2 %% --------------------------------------------------------------------------- -share_peers(Pid, #state{options = [_, {_, true} | _], - local_peers = PDict}) -> - ?Dict:fold(fun(A,Ps,ok) -> sp(Pid, A, Ps), ok end, ok, PDict); - -share_peers(_, _) -> - ok. +share_peers(Pid, #state{options = [_, {_,T} | _], local_peers = PDict}) -> + is_remote(Pid, T) + andalso ?Dict:fold(fun(A,Ps,ok) -> sp(Pid, A, Ps), ok end, ok, PDict). sp(Pid, Alias, Peers) -> lists:foreach(fun({P,C}) -> Pid ! {peer, P, [Alias], C} end, Peers). +is_remote(Pid, T) -> + Node = node(Pid), + Node /= node() andalso lists:member(Node, remotes(T)). + %% --------------------------------------------------------------------------- %% # remote_peer_up/4 %% --------------------------------------------------------------------------- -remote_peer_up(Pid, Aliases, Caps, #state{options = [_, _, {_, true} | _], - service = Svc, - shared_peers = PDict}) -> +remote_peer_up(Pid, Aliases, Caps, #state{options = [_, _, {_,T} | _]} = S) -> + is_remote(Pid, T) + andalso rpu(Pid, Aliases, Caps, S). + +rpu(Pid, Aliases, Caps, #state{service = Svc, shared_peers = PDict}) -> #diameter_service{applications = Apps} = Svc, Key = #diameter_app.alias, - As = lists:filter(fun(A) -> lists:keymember(A, Key, Apps) end, Aliases), - rpu(Pid, Caps, PDict, As); - -remote_peer_up(_, _, _, #state{options = [_, _, {_, false} | _]}) -> - ok. + F = fun(A) -> lists:keymember(A, Key, Apps) end, + rpu(Pid, lists:filter(F, Aliases), Caps, PDict); -rpu(_, _, PDict, []) -> - PDict; -rpu(Pid, Caps, PDict, Aliases) -> +rpu(_, [] = No, _, _) -> + No; +rpu(Pid, Aliases, Caps, PDict) -> erlang:monitor(process, Pid), T = {Pid, Caps}, lists:foreach(fun(A) -> ?Dict:append(A, T, PDict) end, Aliases). @@ -1302,8 +1331,7 @@ rpu(Pid, Caps, PDict, Aliases) -> %% # remote_peer_down/2 %% --------------------------------------------------------------------------- -remote_peer_down(Pid, #state{options = [_, _, {_, true} | _], - shared_peers = PDict}) -> +remote_peer_down(Pid, #state{shared_peers = PDict}) -> lists:foreach(fun(A) -> rpd(Pid, A, PDict) end, ?Dict:fetch_keys(PDict)). rpd(Pid, Alias, PDict) -> @@ -1626,16 +1654,10 @@ info_stats(#state{watchdogT = WatchdogT}) -> info_transport(S) -> PeerD = peer_dict(S, config_dict(S)), - RefsD = dict:map(fun(_, Ls) -> [P || L <- Ls, {peer, {P,_}} <- L] end, - PeerD), - Refs = lists:append(dict:fold(fun(R, Ps, A) -> [[R|Ps] | A] end, - [], - RefsD)), - Stats = diameter_stats:read(Refs), + Stats = diameter_stats:sum(dict:fetch_keys(PeerD)), dict:fold(fun(R, Ls, A) -> - Ps = dict:fetch(R, RefsD), - [[{ref, R} | transport(Ls)] ++ [stats([R|Ps], Stats)] - | A] + Cs = proplists:get_value(R, Stats, []), + [[{ref, R} | transport(Ls)] ++ [{statistics, Cs}] | A] end, [], PeerD). diff --git a/lib/diameter/src/base/diameter_stats.erl b/lib/diameter/src/base/diameter_stats.erl index 8fd5ded300..b68d4af11f 100644 --- a/lib/diameter/src/base/diameter_stats.erl +++ b/lib/diameter/src/base/diameter_stats.erl @@ -28,6 +28,7 @@ -export([reg/2, reg/1, incr/3, incr/1, read/1, + sum/1, flush/1]). %% supervisor callback @@ -77,10 +78,14 @@ reg(Pid, Ref) when is_pid(Pid) -> - call({reg, Pid, Ref}). + try + call({reg, Pid, Ref}) + catch + exit: _ -> false + end. -spec reg(ref()) - -> true. + -> boolean(). reg(Ref) -> reg(self(), Ref). @@ -111,11 +116,19 @@ incr(Ctr) -> %% Retrieve counters for the specified contributors. %% --------------------------------------------------------------------------- +%% Read in the server process to ensure that counters for a dying +%% contributor aren't folded concurrently with select. + -spec read([ref()]) -> [{ref(), [{counter(), integer()}]}]. -read(Refs) -> - read(Refs, false). +read(Refs) + when is_list(Refs) -> + try call({read, Refs, false}) of + L -> to_refdict(L) + catch + exit: _ -> [] + end. read(Refs, B) -> MatchSpec = [{{{'_', '$1'}, '_'}, @@ -124,11 +137,52 @@ read(Refs, B) -> ['$_']}], L = ets:select(?TABLE, MatchSpec), B andalso delete(L), + L. + +to_refdict(L) -> lists:foldl(fun({{C,R}, N}, D) -> orddict:append(R, {C,N}, D) end, orddict:new(), L). %% --------------------------------------------------------------------------- +%% # sum(Refs) +%% +%% Retrieve counters summed over all contributors for each term. +%% --------------------------------------------------------------------------- + +-spec sum([ref()]) + -> [{ref(), [{counter(), integer()}]}]. + +sum(Refs) + when is_list(Refs) -> + try call({read, Refs}) of + L -> [{R, to_ctrdict(Cs)} || {R, [_|_] = Cs} <- L] + catch + exit: _ -> [] + end. + +read_refs(Refs) -> + [{R, readr(R)} || R <- Refs]. + +readr(Ref) -> + MatchSpec = [{{{'_', '$1'}, '_'}, + [?ORCOND([{'=:=', '$1', {const, R}} + || R <- [Ref | pids(Ref)]])], + ['$_']}], + ets:select(?TABLE, MatchSpec). + +pids(Ref) -> + MatchSpec = [{{'$1', '$2'}, + [{'=:=', '$2', {const, Ref}}], + ['$1']}], + ets:select(?TABLE, MatchSpec). + +to_ctrdict(L) -> + lists:foldl(fun({{C,_}, N}, D) -> orddict:update_counter(C, N, D) end, + orddict:new(), + L). + +%% --------------------------------------------------------------------------- %% # flush(Refs) %% %% Retrieve and delete statistics for the specified contributors. @@ -138,11 +192,10 @@ read(Refs, B) -> -> [{ref(), {counter(), integer()}}]. flush(Refs) -> - try - call({flush, Refs}) + try call({read, Refs, true}) of + L -> to_refdict(L) catch - exit: _ -> - [] + exit: _ -> [] end. %% =========================================================================== @@ -186,8 +239,14 @@ handle_call({reg, Pid, Ref}, _From, State) -> B andalso erlang:monitor(process, Pid), {reply, B, State}; -handle_call({flush, Refs}, _From, State) -> - {reply, read(Refs, true), State}; +handle_call({read, Refs, Del}, _From, State) -> + {reply, read(Refs, Del), State}; + +handle_call({read, Refs}, _, State) -> + {reply, read_refs(Refs), State}; + +handle_call({flush, Refs}, _From, State) -> %% from old code + {reply, to_refdict(read(Refs, true)), State}; handle_call(Req, From, State) -> ?UNEXPECTED([Req, From]), diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index f527f7c754..0b15e68ec7 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -226,10 +226,10 @@ recv_R(false = No, _, _, _, _) -> %% transport has gone down collect_avps(Pkt) -> case diameter_codec:collect_avps(Pkt) of - {_Bs, As} -> - As; - As -> - As + {_Error, Avps} -> + Avps; + Avps -> + Avps end. %% recv_R/6 @@ -300,7 +300,7 @@ errors(_, #diameter_packet{header = #diameter_header{version = V}, %% AVP's definition. errors(_, #diameter_packet{errors = [Bs | Es]} = Pkt) - when is_bitstring(Bs) -> + when is_bitstring(Bs) -> %% from old code Pkt#diameter_packet{errors = [3009 | Es]}; %% DIAMETER_COMMAND_UNSUPPORTED 3001 @@ -479,10 +479,9 @@ answer_message(RC, #diameter_caps{origin_host = {OH,_}, origin_realm = {OR,_}}, Dict0, - #diameter_packet{avps = Avps} - = Pkt) -> + Pkt) -> ?LOG({error, RC}, Pkt), - {Dict0, answer_message(OH, OR, RC, Dict0, Avps)}. + {Dict0, answer_message(OH, OR, RC, Dict0, Pkt)}. %% resend/7 @@ -595,71 +594,87 @@ reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt) is_tuple(Msg) -> reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt#diameter_packet{errors = []}); -%% No errors or a diameter_header/avp list. reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) -> - Pkt = encode(Dict, reset(make_answer_packet(Msg, ReqPkt), Dict), Fs), + Pkt = encode(Dict, + reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0), + Fs), incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes send(TPid, Pkt). -%% reset/2 +%% reset/3 %% Header/avps list: send as is. -reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _) -> +reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _, _) -> Pkt; %% No errors to set or errors explicitly ignored. -reset(#diameter_packet{errors = Es} = Pkt, _) +reset(#diameter_packet{errors = Es} = Pkt, _, _) when Es == []; Es == false -> Pkt; %% Otherwise possibly set Result-Code and/or Failed-AVP. -reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict) -> - Pkt#diameter_packet{msg = reset(Msg, Dict, Es)}. - -%% reset/3 - -reset(Msg, Dict, Es) - when is_list(Es) -> - {E3, E5, Fs} = partition(Es), - FailedAVP = failed_avp(Msg, lists:reverse(Fs), Dict), - reset(set(Msg, FailedAVP, Dict), - Dict, - choose(is_answer_message(Msg, Dict), E3, E5)); +reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict, Dict0) -> + {RC, Failed} = select_error(Msg, Es, Dict0), + Pkt#diameter_packet{msg = reset(Msg, Dict, RC, Failed)}. -reset(Msg, Dict, N) - when is_integer(N) -> - ResultCode = rc(Msg, {'Result-Code', N}, Dict), - set(Msg, ResultCode, Dict); - -reset(Msg, _, _) -> - Msg. - -partition(Es) -> - lists:foldl(fun pacc/2, {false, false, []}, Es). - -%% Note that the errors list can contain not only integer() and -%% {integer(), #diameter_avp{}} but also #diameter_avp{}. The latter -%% isn't something that's returned by decode but can be set in a reply -%% for encode. +%% select_error/3 +%% +%% Extract the first appropriate RC or {RC, #diameter_avp{}} +%% pair from an errors list, and accumulate all #diameter_avp{}. +%% +%% RFC 6733: +%% +%% 7.5. Failed-AVP AVP +%% +%% The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides +%% debugging information in cases where a request is rejected or not +%% fully processed due to erroneous information in a specific AVP. The +%% value of the Result-Code AVP will provide information on the reason +%% for the Failed-AVP AVP. A Diameter answer message SHOULD contain an +%% instance of the Failed-AVP AVP that corresponds to the error +%% indicated by the Result-Code AVP. For practical purposes, this +%% Failed-AVP would typically refer to the first AVP processing error +%% that a Diameter node encounters. + +select_error(Msg, Es, Dict0) -> + {RC, Avps} = lists:foldl(fun(T,A) -> select(T, A, Dict0) end, + {is_answer_message(Msg, Dict0), []}, + Es), + {RC, lists:reverse(Avps)}. + +%% Only integer() and {integer(), #diameter_avp{}} are the result of +%% decode. #diameter_avp{} can only be set in a reply for encode. + +select(#diameter_avp{} = A, {RC, As}, _) -> + {RC, [A|As]}; + +select(_, {RC, _} = Acc, _) + when is_integer(RC) -> + Acc; -pacc({RC, #diameter_avp{} = A}, {E3, E5, Acc}) +select({RC, #diameter_avp{} = A}, {IsAns, As} = Acc, Dict0) when is_integer(RC) -> - pacc(RC, {E3, E5, [A|Acc]}); + case is_result(RC, IsAns, Dict0) of + true -> {RC, [A|As]}; + false -> Acc + end; -pacc(#diameter_avp{} = A, {E3, E5, Acc}) -> - {E3, E5, [A|Acc]}; +select(RC, {IsAns, As} = Acc, Dict0) + when is_boolean(IsAns), is_integer(RC) -> + case is_result(RC, IsAns, Dict0) of + true -> {RC, As}; + false -> Acc + end. -pacc(RC, {false, E5, Acc}) - when 3 == RC div 1000 -> - {RC, E5, Acc}; +%% reset/4 -pacc(RC, {E3, false, Acc}) - when 5 == RC div 1000 -> - {E3, RC, Acc}; +reset(Msg, Dict, RC, Avps) -> + FailedAVP = failed_avp(Msg, Avps, Dict), + ResultCode = rc(Msg, RC, Dict), + set(set(Msg, FailedAVP, Dict), ResultCode, Dict). -pacc(_, Acc) -> - Acc. +%% eval_packet/2 eval_packet(Pkt, Fs) -> lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs). @@ -725,29 +740,34 @@ set(Rec, Avps, Dict) -> %% the arity is 1 or {0,1}. In other cases (which probably shouldn't %% exist in practise) we can't know what's appropriate. -rc([MsgName | _], {'Result-Code' = K, RC} = T, Dict) -> - case Dict:avp_arity(MsgName, 'Result-Code') of - 1 -> [T]; +rc(_, B, _) + when is_boolean(B) -> + []; + +rc([MsgName | _], RC, Dict) -> + K = 'Result-Code', + case Dict:avp_arity(MsgName, K) of + 1 -> [{K, RC}]; {0,1} -> [{K, [RC]}]; _ -> [] end; -rc(Rec, T, Dict) -> - rc([Dict:rec2msg(element(1, Rec))], T, Dict). +rc(Rec, RC, Dict) -> + rc([Dict:rec2msg(element(1, Rec))], RC, Dict). %% failed_avp/3 failed_avp(_, [] = No, _) -> No; -failed_avp(Rec, Failed, Dict) -> - [fa(Rec, [{'AVP', Failed}], Dict)]. +failed_avp(Rec, Avps, Dict) -> + [failed(Rec, [{'AVP', Avps}], Dict)]. %% Reply as name and tuple list ... -fa([MsgName | Values], FailedAvp, Dict) -> - R = Dict:msg2rec(MsgName), +failed([MsgName | Values], FailedAvp, Dict) -> + RecName = Dict:msg2rec(MsgName), try - Dict:'#info-'(R, {index, 'Failed-AVP'}), + Dict:'#info-'(RecName, {index, 'Failed-AVP'}), {'Failed-AVP', [FailedAvp]} catch error: _ -> @@ -758,8 +778,10 @@ fa([MsgName | Values], FailedAvp, Dict) -> end; %% ... or record. -fa(Rec, FailedAvp, Dict) -> +failed(Rec, FailedAvp, Dict) -> try + RecName = element(1, Rec), + Dict:'#info-'(RecName, {index, 'Failed-AVP'}), {'Failed-AVP', [FailedAvp]} catch error: _ -> @@ -838,12 +860,14 @@ fa(Rec, FailedAvp, Dict) -> %% answer_message/5 -answer_message(OH, OR, RC, Dict0, Avps) -> +answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps, + errors = Es}) -> {Code, _, Vid} = Dict0:avp_header('Session-Id'), ['answer-message', {'Origin-Host', OH}, {'Origin-Realm', OR}, - {'Result-Code', RC} - | session_id(Code, Vid, Dict0, Avps)]. + {'Result-Code', RC}] + ++ session_id(Code, Vid, Dict0, Avps) + ++ failed_avp(RC, Es). session_id(Code, Vid, Dict0, Avps) when is_list(Avps) -> @@ -855,6 +879,15 @@ session_id(Code, Vid, Dict0, Avps) [] end. +%% Note that this should only match 5xxx result codes currently but +%% don't bother distinguishing this case. +failed_avp(RC, [{RC, Avp} | _]) -> + [{'Failed-AVP', [{'AVP', [Avp]}]}]; +failed_avp(RC, [_ | Es]) -> + failed_avp(RC, Es); +failed_avp(_, [] = No) -> + No. + %% find_avp/3 find_avp(Code, Vid, Avps) @@ -1479,12 +1512,14 @@ send({TPid, Pkt, #request{handler = Pid} = Req, SvcName, Timeout, TRef}) -> Req#request{handler = self()}, SvcName, Timeout), - Pid ! reref(receive T -> T end, Ref, TRef). - -reref({T, Ref, R}, Ref, TRef) -> - {T, TRef, R}; -reref(T, _, _) -> - T. + receive + {answer, _, _, _, _} = A -> + Pid ! A; + {failover = T, Ref} -> + Pid ! {T, TRef}; + T -> + exit({timeout, Ref, TPid} = T) + end. %% send/2 @@ -1559,7 +1594,7 @@ resend_request(Pkt0, store_request(TPid, Bin, Req, Timeout) -> Seqs = diameter_codec:sequence_numbers(Bin), - TRef = erlang:start_timer(Timeout, self(), timeout), + TRef = erlang:start_timer(Timeout, self(), TPid), ets:insert(?REQUEST_TABLE, {Seqs, Req, TRef}), ets:member(?REQUEST_TABLE, TPid) orelse (self() ! {failover, TRef}), %% failover/1 may have missed diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 073a415d10..88ccf630e2 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -47,6 +47,14 @@ -define(BASE, ?DIAMETER_DICT_COMMON). +-define(IS_NATURAL(N), (is_integer(N) andalso 0 =< N)). + +-define(CHOOSE(B,T,F), if (B) -> T; true -> F end). + +-record(config, + {suspect = 1 :: non_neg_integer(), %% OKAY -> SUSPECT + okay = 3 :: non_neg_integer()}). %% REOPEN -> OKAY + -record(watchdog, {%% PCB - Peer Control Block; see RFC 3539, Appendix A status = initial :: initial | okay | suspect | down | reopen, @@ -54,7 +62,8 @@ tw :: 6000..16#FFFFFFFF | {module(), atom(), list()}, %% {M,F,A} -> integer() >= 0 num_dwa = 0 :: -1 | non_neg_integer(), - %% number of DWAs received during reopen + %% number of DWAs received in reopen, + %% or number of timeouts before okay -> suspect %% end PCB parent = self() :: pid(), %% service process transport :: pid() | undefined, %% peer_fsm process @@ -64,7 +73,8 @@ %% term passed into diameter_service with incoming message sequence :: diameter:sequence(), %% mask restrict :: {diameter:restriction(), boolean()}, - shutdown = false :: boolean()}). + shutdown = false :: boolean(), + config :: #config{}}). %% --------------------------------------------------------------------------- %% start/2 @@ -129,7 +139,8 @@ i({Ack, T, Pid, {RecvData, receive_data = RecvData, dictionary = Dict0, sequence = Mask, - restrict = {Restrict, lists:member(node(), Nodes)}}. + restrict = {Restrict, lists:member(node(), Nodes)}, + config = config(Opts)}. wait(Ref, Pid) -> receive @@ -139,6 +150,27 @@ wait(Ref, Pid) -> exit({shutdown, D}) end. +%% config/1 +%% +%% Could also configure counts for SUSPECT to DOWN and REOPEN to DOWN, +%% but don't. + +config(Opts) -> + Config = proplists:get_value(watchdog_config, Opts, []), + is_list(Config) orelse config_error({watchdog_config, Config}), + lists:foldl(fun config/2, #config{}, Config). %% ^ added in old code + +config({suspect, N}, Rec) + when ?IS_NATURAL(N) -> + Rec#config{suspect = N}; + +config({okay, N}, Rec) + when ?IS_NATURAL(N) -> + Rec#config{okay = N}; + +config(T, _) -> %% added in old code + config_error(T). + %% start/5 start(T, Opts, Mask, Nodes, Dict0, Svc) -> @@ -193,7 +225,8 @@ dict0(_, _, Acc) -> Acc. config_error(T) -> - ?ERROR({configuration_error, T}). + diameter_lib:error_report(configuration_error, T), + exit({shutdown, {configuration_error, T}}). %% handle_call/3 @@ -219,6 +252,17 @@ handle_info(T, #watchdog{} = State) -> ?LOG(stop, T), event(T, State, State#watchdog{status = down}), {stop, {shutdown, T}, State} + end; + +handle_info(T, State) -> %% started in old code + handle_info(T, upgrade(State)). + +upgrade(State) -> + case erlang:append_element(State, #config{}) of + #watchdog{status = okay, config = #config{suspect = OS}} = S -> + S#watchdog{num_dwa = OS}; + #watchdog{} = S -> + S end. close({'DOWN', _, process, TPid, {shutdown, Reason}}, @@ -331,11 +375,13 @@ transition({accepted = T, TPid}, #watchdog{transport = TPid, transition({open, TPid, Hosts, _} = Open, #watchdog{transport = TPid, status = initial, - restrict = {_, R}} + restrict = {_,R}, + config = #config{suspect = OS}} = S) -> case okay(getr(restart), Hosts, R) of okay -> - set_watchdog(S#watchdog{status = okay}); + set_watchdog(S#watchdog{status = okay, + num_dwa = OS}); reopen -> transition(Open, S#watchdog{status = down}) end; @@ -347,15 +393,22 @@ transition({open, TPid, Hosts, _} = Open, transition({open = Key, TPid, _Hosts, T}, #watchdog{transport = TPid, - status = down} + status = down, + config = #config{suspect = OS, + okay = RO}} = S) -> - %% Store the info we need to notify the parent to reopen the - %% connection after the requisite DWA's are received, at which - %% time we eraser(open). The reopen message is a later addition, - %% to communicate the new capabilities as soon as they're known. - putr(Key, {TPid, T}), - set_watchdog(send_watchdog(S#watchdog{status = reopen, - num_dwa = 0})); + case RO of + 0 -> %% non-standard: skip REOPEN + set_watchdog(S#watchdog{status = okay, + num_dwa = OS}); + _ -> + %% Store the info we need to notify the parent to reopen + %% the connection after the requisite DWA's are received, + %% at which time we eraser(open). + putr(Key, {TPid, T}), + set_watchdog(send_watchdog(S#watchdog{status = reopen, + num_dwa = 0})) + end; %% OKAY Connection down CloseConnection() %% Failover() @@ -374,7 +427,7 @@ transition({'DOWN', _, process, TPid, _Reason}, #watchdog{transport = TPid, status = T} = S) -> - set_watchdog(S#watchdog{status = case T of initial -> T; _ -> down end, + set_watchdog(S#watchdog{status = ?CHOOSE(initial == T, T, down), pending = false, transport = undefined}); @@ -452,7 +505,9 @@ set_watchdog(#watchdog{tw = TwInit, tref = TRef} = S) -> cancel(TRef), - S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)}. + S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)}; +set_watchdog(stop = No) -> + No. cancel(undefined) -> ok; @@ -553,22 +608,27 @@ rcv(_, #watchdog{status = okay} = S) -> %% SUSPECT Receive non-DWA Failback() %% SetWatchdog() OKAY -rcv('DWA', #watchdog{status = suspect} = S) -> +rcv('DWA', #watchdog{status = suspect, config = #config{suspect = OS}} = S) -> set_watchdog(S#watchdog{status = okay, + num_dwa = OS, pending = false}); -rcv(_, #watchdog{status = suspect} = S) -> - set_watchdog(S#watchdog{status = okay}); +rcv(_, #watchdog{status = suspect, config = #config{suspect = OS}} = S) -> + set_watchdog(S#watchdog{status = okay, + num_dwa = OS}); %% REOPEN Receive DWA & Pending = FALSE %% NumDWA == 2 NumDWA++ %% Failback() OKAY rcv('DWA', #watchdog{status = reopen, - num_dwa = 2 = N} - = S) -> + num_dwa = N, + config = #config{suspect = OS, + okay = RO}} + = S) + when N+1 == RO -> S#watchdog{status = okay, - num_dwa = N+1, + num_dwa = OS, pending = false}; %% REOPEN Receive DWA & Pending = FALSE @@ -607,9 +667,17 @@ timeout(#watchdog{status = T, %% Pending SetWatchdog() SUSPECT timeout(#watchdog{status = okay, - pending = true} - = S) -> - S#watchdog{status = suspect}; + pending = true, + num_dwa = N} + = S) -> + case N of + 1 -> + S#watchdog{status = suspect}; + 0 -> %% non-standard: never move to suspect + S; + N -> %% non-standard: more timeouts before moving + S#watchdog{num_dwa = N-1} + end; %% SUSPECT Timer expires CloseConnection() %% SetWatchdog() DOWN diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 80036879ea..e687145263 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -574,12 +574,12 @@ cs_enumerated_avp({AvpName, Values}) -> lists:flatmap(fun(V) -> c_enumerated_avp(AvpName, V) end, Values). c_enumerated_avp(AvpName, {_,I}) -> - [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32/integer>>)], + [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32>>)], [], [?TERM(I)]}, {?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)], [], - [?TERM(<<I:32/integer>>)]}]. + [?TERM(<<I:32>>)]}]. %%% ------------------------------------------------------------------------ %%% msg_header/1 @@ -700,7 +700,7 @@ c_empty_value({Name, _, _, _}) -> c_empty_value({Name, _}) -> {?clause, [?Atom(Name)], [], - [?TERM(<<0:32/integer>>)]}. + [?TERM(<<0:32>>)]}. %%% ------------------------------------------------------------------------ %%% # dict/0 diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl index 132088b514..cbbba714ac 100644 --- a/lib/diameter/src/transport/diameter_tcp.erl +++ b/lib/diameter/src/transport/diameter_tcp.erl @@ -100,6 +100,18 @@ %% # start/3 %% --------------------------------------------------------------------------- +-spec start({accept, Ref}, Svc, [Opt]) + -> {ok, pid(), [inet:ip_address()]} + when Ref :: diameter:transport_ref(), + Svc :: #diameter_service{}, + Opt :: diameter:transport_opt(); + ({connect, Ref}, Svc, [Opt]) + -> {ok, pid(), [inet:ip_address()]} + | {ok, pid()} + when Ref :: diameter:transport_ref(), + Svc :: #diameter_service{}, + Opt :: diameter:transport_opt(). + start({T, Ref}, #diameter_service{capabilities = Caps}, Opts) -> diameter_tcp_sup:start(), %% start tcp supervisors on demand {Mod, Rest} = split(Opts), @@ -172,7 +184,7 @@ i({T, Ref, Mod, Pid, Opts, Addrs}) OwnOpts, ?DEFAULT_FRAGMENT_TIMEOUT), ?IS_TIMEOUT(Tmo) orelse ?ERROR({fragment_timer, Tmo}), - Sock = i(T, Ref, Mod, Pid, SslOpts, Rest, Addrs), + Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs), MPid ! {stop, self()}, %% tell the monitor to die M = if SslOpts -> ssl; true -> Mod end, setopts(M, Sock), @@ -199,14 +211,21 @@ i(#monitor{parent = Pid, transport = TPid} = S) -> i({listen, LRef, APid, {Mod, Opts, Addrs}}) -> {[LA, LP], Rest} = proplists:split(Opts, [ip, port]), - LAddr = get_addr(LA, Addrs), + LAddrOpt = get_addr(LA, Addrs), LPort = get_port(LP), - {ok, LSock} = Mod:listen(LPort, gen_opts(LAddr, Rest)), + {ok, LSock} = Mod:listen(LPort, gen_opts(LAddrOpt, Rest)), + LAddr = laddr(LAddrOpt, Mod, LSock), true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}), proc_lib:init_ack({ok, self(), {LAddr, LSock}}), erlang:monitor(process, APid), start_timer(#listener{socket = LSock}). +laddr([], Mod, Sock) -> + {ok, {Addr, _Port}} = sockname(Mod, Sock), + Addr; +laddr([{ip, Addr}], _, _) -> + Addr. + own(Opts) -> {Own, Rest} = proplists:split(Opts, [fragment_timer]), {lists:append(Own), Rest}. @@ -225,17 +244,19 @@ ssl_opts([{ssl_options, Opts}]) ssl_opts(L) -> ?ERROR({ssl_options, L}). -%% i/7 +%% init/7 %% Establish a TLS connection before capabilities exchange ... -i(Type, Ref, Mod, Pid, true, Opts, Addrs) -> - i(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs); +init(Type, Ref, Mod, Pid, true, Opts, Addrs) -> + init(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs); %% ... or not. -i(Type, Ref, Mod, Pid, _, Opts, Addrs) -> - i(Type, Ref, Mod, Pid, Opts, Addrs). +init(Type, Ref, Mod, Pid, _, Opts, Addrs) -> + init(Type, Ref, Mod, Pid, Opts, Addrs). + +%% init/6 -i(accept = T, Ref, Mod, Pid, Opts, Addrs) -> +init(accept = T, Ref, Mod, Pid, Opts, Addrs) -> {LAddr, LSock} = listener(Ref, {Mod, Opts, Addrs}), proc_lib:init_ack({ok, self(), [LAddr]}), Sock = ok(accept(Mod, LSock)), @@ -243,17 +264,28 @@ i(accept = T, Ref, Mod, Pid, Opts, Addrs) -> diameter_peer:up(Pid), Sock; -i(connect = T, Ref, Mod, Pid, Opts, Addrs) -> +init(connect = T, Ref, Mod, Pid, Opts, Addrs) -> {[LA, RA, RP], Rest} = proplists:split(Opts, [ip, raddr, rport]), - LAddr = get_addr(LA, Addrs), - RAddr = get_addr(RA, []), + LAddrOpt = get_addr(LA, Addrs), + RAddr = get_addr(RA), RPort = get_port(RP), - proc_lib:init_ack({ok, self(), [LAddr]}), - Sock = ok(connect(Mod, RAddr, RPort, gen_opts(LAddr, Rest))), + proc_lib:init_ack(init_rc(LAddrOpt)), + Sock = ok(connect(Mod, RAddr, RPort, gen_opts(LAddrOpt, Rest))), publish(Mod, T, Ref, Sock), - diameter_peer:up(Pid, {RAddr, RPort}), + up(Pid, {RAddr, RPort}, LAddrOpt, Mod, Sock), Sock. +init_rc([{ip, Addr}]) -> + {ok, self(), [Addr]}; +init_rc([]) -> + {ok, self()}. + +up(Pid, Remote, [{ip, _Addr}], _, _) -> + diameter_peer:up(Pid, Remote); +up(Pid, Remote, [], Mod, Sock) -> + {Addr, _Port} = ok(sockname(Mod, Sock)), + diameter_peer:up(Pid, Remote, [Addr]). + publish(Mod, T, Ref, Sock) -> true = diameter_reg:add_new({?MODULE, T, {Ref, Sock}}), putr(?INFO_KEY, {Mod, Sock}). %% for info/1 @@ -281,10 +313,17 @@ l([], LRef, T) -> {ok, _, AS} = diameter_tcp_sup:start_child({listen, LRef, self(), T}), AS. +%% get_addr/1 + +get_addr(As) -> + diameter_lib:ipaddr(addr(As, [])). + %% get_addr/2 +get_addr([], []) -> + []; get_addr(As, Def) -> - diameter_lib:ipaddr(addr(As, Def)). + [{ip, diameter_lib:ipaddr(addr(As, Def))}]. %% Take the first address from the service if several are unspecified. addr([], [Addr | _]) -> @@ -305,14 +344,10 @@ get_port(Ps) -> %% gen_opts/2 -gen_opts(LAddr, Opts) -> +gen_opts(LAddrOpt, Opts) -> {L,_} = proplists:split(Opts, [binary, packet, active]), [[],[],[]] == L orelse ?ERROR({reserved_options, Opts}), - [binary, - {packet, 0}, - {active, once}, - {ip, LAddr} - | Opts]. + [binary, {packet, 0}, {active, once}] ++ LAddrOpt ++ Opts. %% --------------------------------------------------------------------------- %% # ports/1 diff --git a/lib/diameter/test/Makefile b/lib/diameter/test/Makefile index 061f0bcbef..9719c67b32 100644 --- a/lib/diameter/test/Makefile +++ b/lib/diameter/test/Makefile @@ -93,6 +93,11 @@ info: @$(call list,HRL_FILES) @echo @$(call list,SUITES) + @echo + @echo erl = $(shell which erl) + @erl -noinput \ + -eval 'io:format("diameter = ~s~n", [code:lib_dir(diameter)])' \ + -s init stop @echo ======================================== help: diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl index 89c78d8b57..071b1a1177 100644 --- a/lib/diameter/test/diameter_3xxx_SUITE.erl +++ b/lib/diameter/test/diameter_3xxx_SUITE.erl @@ -40,9 +40,10 @@ send_unknown_application/1, send_unknown_command/1, send_ok/1, - send_invalid_avp_bits/1, + send_invalid_hdr_bits/1, send_missing_avp/1, send_ignore_missing_avp/1, + send_5xxx_missing_avp/1, send_double_error/1, send_3xxx/1, send_5xxx/1, @@ -136,9 +137,10 @@ tc() -> [send_unknown_application, send_unknown_command, send_ok, - send_invalid_avp_bits, + send_invalid_hdr_bits, send_missing_avp, send_ignore_missing_avp, + send_5xxx_missing_avp, send_double_error, send_3xxx, send_5xxx]. @@ -216,27 +218,26 @@ send_ok([_,_]) -> send_ok(Config) -> send_ok(?group(Config)). -%% send_invalid_avp_bits/1 +%% send_invalid_hdr_bits/1 %% -%% Send a request with an incorrect length on the optional -%% Origin-State-Id that a callback ignores. +%% Send a request with an incorrect E-bit that a callback ignores. %% Callback answers. -send_invalid_avp_bits([callback, _]) -> +send_invalid_hdr_bits([callback, _]) -> #diameter_base_STA{'Result-Code' = 2001, %% SUCCESS 'Failed-AVP' = [], 'AVP' = []} = call(); %% diameter answers. -send_invalid_avp_bits([_,_]) -> - #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS +send_invalid_hdr_bits([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS 'Failed-AVP' = [], 'AVP' = []} = call(); -send_invalid_avp_bits(Config) -> - send_invalid_avp_bits(?group(Config)). +send_invalid_hdr_bits(Config) -> + send_invalid_hdr_bits(?group(Config)). %% send_missing_avp/1 %% @@ -280,10 +281,35 @@ send_ignore_missing_avp([_,_]) -> send_ignore_missing_avp(Config) -> send_ignore_missing_avp(?group(Config)). +%% send_5xxx_missing_avp/1 +%% +%% Send a request with a missing AVP that a callback answers +%% with {answer_message, 5005}. + +%% RFC 6733 allows 5xxx in an answer-message. +send_5xxx_missing_avp([_, rfc6733]) -> + #'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +%% RFC 3588 doesn't: sending answer fails. +send_5xxx_missing_avp([_, rfc3588]) -> + {error, timeout} = call(); + +%% Callback answers, ignores the error +send_5xxx_missing_avp([_,_]) -> + #diameter_base_STA{'Result-Code' = 2001, %% SUCCESS + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_5xxx_missing_avp(Config) -> + send_5xxx_missing_avp(?group(Config)). + %% send_double_error/1 %% -%% Send a request with both an incorrect length on the optional -%% Origin-State-Id and a missing AVP. +%% Send a request with both an invalid E-bit and a missing AVP. %% Callback answers with STA. send_double_error([callback, _]) -> @@ -294,8 +320,8 @@ send_double_error([callback, _]) -> %% diameter answers with answer-message. send_double_error([_,_]) -> - #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS - 'Failed-AVP' = [_], + #'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS + 'Failed-AVP' = [], 'AVP' = []} = call(); @@ -392,24 +418,21 @@ prepare(Pkt, Caps, T) T == send_5xxx -> sta(Pkt, Caps); -prepare(Pkt0, Caps, send_invalid_avp_bits) -> - Req0 = sta(Pkt0, Caps), - %% Append an Origin-State-Id with an incorrect AVP Length in order - %% to force 3009. - Req = Req0#diameter_base_STR{'Origin-State-Id' = [7]}, - #diameter_packet{bin = Bin} +prepare(Pkt0, Caps, send_invalid_hdr_bits) -> + Req = sta(Pkt0, Caps), + %% Set the E-bit to force 3008. + #diameter_packet{bin = <<H:34, 0:1, T/bitstring>>} = Pkt = diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}), - Offset = size(Bin) - 12 + 5, - <<H:Offset/binary, Len:24, T/binary>> = Bin, - Pkt#diameter_packet{bin = <<H/binary, (Len + 2):24, T/binary>>}; + Pkt#diameter_packet{bin = <<H:34, 1:1, T/bitstring>>}; prepare(Pkt0, Caps, send_double_error) -> - dehost(prepare(Pkt0, Caps, send_invalid_avp_bits)); + dehost(prepare(Pkt0, Caps, send_invalid_hdr_bits)); prepare(Pkt, Caps, T) when T == send_missing_avp; - T == send_ignore_missing_avp -> + T == send_ignore_missing_avp; + T == send_5xxx_missing_avp -> Req = sta(Pkt, Caps), dehost(diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req})). @@ -480,9 +503,7 @@ request(send_3xxx, _Req, _Caps) -> request(send_5xxx, _Req, _Caps) -> {answer_message, 5999}; -request(send_invalid_avp_bits, Req, Caps) -> - #diameter_base_STR{'Origin-State-Id' = []} - = Req, +request(send_invalid_hdr_bits, Req, Caps) -> %% Default errors field but a non-answer-message and only 3xxx %% errors detected means diameter sets neither Result-Code nor %% Failed-AVP. @@ -495,7 +516,10 @@ request(T, Req, Caps) request(send_ignore_missing_avp, Req, Caps) -> {reply, #diameter_packet{msg = answer(Req, Caps), - errors = false}}. %% ignore errors + errors = false}}; %% ignore errors + +request(send_5xxx_missing_avp, _Req, _Caps) -> + {answer_message, 5005}. %% MISSING_AVP answer(Req, Caps) -> #diameter_base_STR{'Session-Id' = SId} diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 209f72adf1..1e262895a6 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_SUITE.erl @@ -56,7 +56,7 @@ %% =========================================================================== suite() -> - [{timetrap, {seconds, 10}}]. + [{timetrap, {seconds, 60}}]. all() -> [keys, diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl index a4e4195a19..8c9bb67e61 100644 --- a/lib/diameter/test/diameter_capx_SUITE.erl +++ b/lib/diameter/test/diameter_capx_SUITE.erl @@ -34,6 +34,7 @@ %% testcases -export([start/1, + vendor_id/1, start_services/1, add_listeners/1, s_no_common_application/1, @@ -69,7 +70,7 @@ -define(HOST(Name), Name ++ "." ++ ?REALM). %% Config for diameter:start_service/2. --define(SERVICE(Name), +-define(SERVICE, [{'Origin-Realm', ?REALM}, {'Host-IP-Address', [?ADDR]}, {'Vendor-Id', 12345}, @@ -103,6 +104,7 @@ suite() -> [{timetrap, {seconds, 60}}]. all() -> [start, + vendor_id, start_services, add_listeners] ++ [{group, D, P} || D <- ?DICTS, P <- [[], [parallel]]] @@ -128,6 +130,7 @@ end_per_group(_, _) -> end_per_testcase(N, _) when N == start; + N == vendor_id; N == start_services; N == add_listeners; N == remove_listeners; @@ -156,9 +159,27 @@ tc() -> start(_Config) -> ok = diameter:start(). +%% Ensure that both integer and list-valued vendor id's can be +%% configured in a 'Vendor-Specific-Application-Id, the arity having +%% changed between RFC 3588 and RFC 6733. +vendor_id(_Config) -> + [] = ?util:run([[fun vid/1, V] || V <- [1, [1], [1,2], x]]). + +vid(V) -> + RC = diameter:start_service(make_ref(), + [{'Vendor-Specific-Application-Id', + [[{'Vendor-Id', V}]]} + | ?SERVICE]), + vid(V, RC). + +vid(x, {error, _}) -> + ok; +vid(_, ok) -> + ok. + start_services(_Config) -> - ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)), - ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)). + ok = diameter:start_service(?SERVER, ?SERVICE), + ok = diameter:start_service(?CLIENT, ?SERVICE). %% One server that responds only to base accounting, one that responds %% to both this and the common application. Share a common service just @@ -423,7 +444,7 @@ connect(Config, T, Opts) -> {CRef, LRef}. connect(LRef, Opts) -> - [PortNr] = ?util:lport(tcp, LRef, 20), + [PortNr] = ?util:lport(tcp, LRef), {ok, CRef} = diameter:add_transport(?CLIENT, {connect, opts(PortNr, Opts)}), CRef. diff --git a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl index bce3d78a37..49f2158b1a 100644 --- a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl +++ b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl @@ -71,6 +71,6 @@ dec('AR', #diameter_packet dec('BR', #diameter_packet {msg = #recv_BR{'Origin-Host' = ?HOST, 'Origin-Realm' = ?REALM}, - errors = [{5008, ?NOT_MANDATORY_YYY}, - {5001, ?MANDATORY_XXX}]}) -> + errors = [{5001, ?MANDATORY_XXX}, + {5008, ?NOT_MANDATORY_YYY}]}) -> ok. diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index dc8cbffc83..24d4c7665e 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -65,7 +65,7 @@ lib(N, {_,_} = T) -> lib(IP, B) -> LA = tuple_to_list(IP), {SA,Fun} = ip(LA), - [] = run([[fun lib/4, IP, B, Fun, A] || A <- [IP, LA, SA]]). + [] = run([[fun lib/4, IP, B, Fun, A] || A <- [IP, SA]]). lib(IP, B, Fun, A) -> try Fun(A) of @@ -78,10 +78,10 @@ lib(IP, B, Fun, A) -> ip([_,_,_,_] = A) -> [$.|S] = lists:append(["." ++ integer_to_list(N) || N <- A]), - {S, fun diameter_lib:ip4address/1}; + {S, fun diameter_lib:ipaddr/1}; ip([_,_,_,_,_,_,_,_] = A) -> [$:|S] = lists:flatten([":" ++ io_lib:format("~.16B", [N]) || N <- A]), - {S, fun diameter_lib:ip6address/1}. + {S, fun diameter_lib:ipaddr/1}. %% ------------------------------------------------------------------------ %% base/1 @@ -265,7 +265,7 @@ arity(M, Name, AvpName, Rec) -> %% enum/3 enum(M, Name, {_,E}) -> - B = <<E:32/integer>>, + B = <<E:32>>, B = M:avp(encode, E, Name), E = M:avp(decode, B, Name). @@ -322,15 +322,15 @@ values('Unsigned64') -> values('Float32') -> E = (1 bsl 8) - 2, F = (1 bsl 23) - 1, - <<Mx:32/float>> = <<0:1/integer, E:8/integer, F:23/integer>>, - <<Mn:32/float>> = <<1:1/integer, E:8/integer, F:23/integer>>, + <<Mx:32/float>> = <<0:1, E:8, F:23>>, + <<Mn:32/float>> = <<1:1, E:8, F:23>>, {[0.0, infinity, '-infinity', Mx, Mn], [0]}; values('Float64') -> E = (1 bsl 11) - 2, F = (1 bsl 52) - 1, - <<Mx:64/float>> = <<0:1/integer, E:11/integer, F:52/integer>>, - <<Mn:64/float>> = <<1:1/integer, E:11/integer, F:52/integer>>, + <<Mx:64/float>> = <<0:1, E:11, F:52>>, + <<Mn:64/float>> = <<1:1, E:11, F:52>>, {[0.0, infinity, '-infinity', Mx, Mn], [0]}; values('Address') -> diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index 79bf9d32db..81722c8dca 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -31,8 +31,7 @@ %% testcases -export([format/1, format/2, replace/1, replace/2, - generate/1, generate/4, - examples/1]). + generate/1, generate/4]). -export([dict/0]). %% fake dictionary module @@ -328,14 +327,6 @@ "@codecs mymod " "Origin-Host Origin-Realm\n&"}]}]). -%% Standard dictionaries in examples/dict. --define(EXAMPLES, [rfc4004_mip, - rfc4005_nas, - rfc4006_cc, - rfc4072_eap, - rfc4590_digest, - rfc4740_sip]). - %% =========================================================================== suite() -> @@ -344,8 +335,7 @@ suite() -> all() -> [format, replace, - generate, - examples]. + generate]. %% Error handling testcases will make an erroneous dictionary out of %% the base dictionary and check that the expected error results. @@ -429,41 +419,6 @@ generate(Mods, Bin, N, Mode) -> andalso ({ok, _} = compile:file(File ++ ".erl", [return_errors])). %% =========================================================================== -%% examples/1 -%% -%% Compile dictionaries extracted from various standards. - -examples(_Config) -> - Dir = filename:join([code:lib_dir(diameter, examples), "dict"]), - [D || D <- ?EXAMPLES, _ <- [examples(?S(D), Dir)]]. - -examples(Dict, Dir) -> - {Name, Pre} = make_name(Dict), - ok = diameter_make:codec(filename:join([Dir, Dict ++ ".dia"]), - [{name, Name}, - {prefix, Pre}, - inherits("rfc3588_base") - | opts(Dict)]), - {ok, _, _} = compile:file(Name ++ ".erl", [return]). - -opts(M) - when M == "rfc4006_cc"; - M == "rfc4072_eap" -> - [inherits("rfc4005_nas")]; -opts("rfc4740_sip") -> - [inherits("rfc4590_digest")]; -opts(_) -> - []. - -inherits(File) -> - {Name, _} = make_name(File), - {inherits, File ++ "/" ++ Name}. - -make_name(File) -> - {R, [$_|N]} = lists:splitwith(fun(C) -> C /= $_ end, File), - {string:join(["diameter_gen", N, R], "_"), "diameter_" ++ N}. - -%% =========================================================================== modify(Bin, Mods) -> lists:foldl(fun re/2, Bin, Mods). diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl new file mode 100644 index 0000000000..46ff63756d --- /dev/null +++ b/lib/diameter/test/diameter_config_SUITE.erl @@ -0,0 +1,272 @@ +%% coding: utf-8 +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Test service and transport config. In particular, of the detection +%% of config errors. +%% + +-module(diameter_config_SUITE). + +-export([suite/0, + all/0]). + +%% testcases +-export([start/1, + start_service/1, + add_transport/1, + stop/1]). + +-define(util, diameter_util). + +%% Lists of {Key, GoodConfigList, BadConfigList} with which to +%% configure. + +-define(SERVICE_CONFIG, + [{application, + [[[{dictionary, diameter_gen_base_rfc6733}, + {module, ?MODULE}]] + | [[[{dictionary, D}, + {module, M}, + {alias, A}, + {state, S}, + {answer_errors, AE}, + {request_errors, RE}, + {call_mutates_state, C}]] + || D <- [diameter_gen_base_rfc3588, diameter_gen_base_rfc6733], + M <- [?MODULE, [?MODULE, now()]], + A <- [0, common, make_ref()], + S <- [[], make_ref()], + AE <- [report, callback, discard], + RE <- [answer_3xxx, answer, callback], + C <- [true, false]]], + [[x], + [[]], + [[{dictionary, diameter_gen_base_rfc3588}]], + [[{module, ?MODULE}]] + | [[[{dictionary, diameter_gen_base_rfc6733}, + {module, ?MODULE}, + {K,x}]] + || K <- [answer_errors, + request_errors, + call_mutates_state]]]}, + {restrict_connections, + [[false], [node], [nodes], [[node(), node()]]], + []}, + {sequence, + [[{0,32}], [{1,31}]], + [[{2,31}]]}, + {share_peers, + [[true], + [false], + [[node()]]], + [[x]]}, + {use_shared_peers, + [[true], + [false], + [[node(), node()]]], + [[x]]}, + {invalid_option, %% invalid service options are rejected + [], + [[x], + [x,x]]}]). + +-define(TRANSPORT_CONFIG, + [{transport_module, + [[?MODULE]], + [[[?MODULE]]]}, + {transport_config, + [[{}, 3000], + [{}, infinity]], + [[{}, x]]}, + {applications, + [[[1, a, [x]]]], + [[x]]}, + {capabilities, + [[[{'Origin-Host', "diameter.erlang.org"}]], + [[{'Origin-Realm', "erlang.org"}]]] + ++ [[[{'Host-IP-Address', L}]] + || L <- [[{127,0,0,1}], + ["127.0.0.1"], + ["127.0.0.1", "FFFF::1", "::1", {1,2,3,4,5,6,7,8}]]] + ++ [[[{'Product-Name', N}]] + || N <- [["Product", $-, ["Name"]], + "Norðurálfa", + "ᚠᚢᚦᚨᚱᚲ"]] + ++ [[[{K,V}]] + || K <- ['Vendor-Id', + 'Origin-State-Id', + 'Firmware-Revision'], + V <- [0, 256, 16#FFFF]] + ++ [[[{K,V}]] + || K <- ['Supported-Vendor-Id', + 'Auth-Application-Id', + 'Acct-Application-Id', + 'Inband-Security-Id'], + V <- [[17], [0, 256, 16#FFFF]]] + ++ [[[{'Vendor-Specific-Application-Id', + [[{'Vendor-Id', V}, + {'Auth-Application-Id', [0]}, + {'Acct-Application-Id', [4]}]]}]] + || V <- [1, [1]]], + [[x], [[{'Origin-Host', "ᚠᚢᚦᚨᚱᚲ"}]]] + ++ [[[{'Host-IP-Address', A}]] + || A <- [{127,0,0,1}]] + ++ [[[{'Product-Name', N}]] + || N <- [x, 1]] + ++ [[[{K,V}]] + || K <- ['Vendor-Id', + 'Origin-State-Id', + 'Firmware-Revision'], + V <- [x, [0], -1, 1 bsl 32]] + ++ [[[{K,V}]] + || K <- ['Supported-Vendor-Id', + 'Auth-Application-Id', + 'Acct-Application-Id', + 'Inband-Security-Id'], + V <- [x, 17, [-1], [1 bsl 32]]] + ++ [[[{'Vendor-Specific-Application-Id', V}]] + || V <- [x, + [[{'Vendor-Id', 1 bsl 32}]], + [[{'Auth-Application-Id', 1}]]]]}, + {capabilities_cb, + [[x]], + []}, + {capx_timeout, + [[3000]], + [[{?MODULE, tmo, []}]]}, + {disconnect_cb, + [[x]], + []}, + {length_errors, + [[exit], [handle], [discard]], + [[x]]}, + {reconnect_timer, + [[3000]], + [[infinity]]}, + {watchdog_timer, + [[3000], + [{?MODULE, tmo, []}]], + [[infinity], + [-1]]}, + {watchdog_config, + [[[{okay, 0}, {suspect, 0}]], + [[{okay, 1}]], + [[{suspect, 2}]]], + [[x], + [[{open, 0}]]]}, + {private, + [[x]], + []}, + {invalid_option, %% invalid transport options are silently ignored + [[x], + [x,x]], + []}]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [start, + start_service, + add_transport, + stop]. + +%% =========================================================================== + +start(_) -> + ok = diameter:start(). + +start_service(T) + when is_tuple(T) -> + do(fun start/3, T); + +start_service(_) -> + [] = ?util:run([{?MODULE, start_service, [T]} + || T <- [lists:keyfind(capabilities, 1, ?TRANSPORT_CONFIG) + | ?SERVICE_CONFIG]]). + +add_transport(T) + when is_tuple(T) -> + do(fun add/3, T); + +add_transport(_) -> + [] = ?util:run([{?MODULE, add_transport, [T]} + || T <- ?TRANSPORT_CONFIG]). + +stop(_) -> + ok = diameter:stop(). + +%% =========================================================================== + +%% do/2 + +do(F, {Key, Good, Bad}) -> + F(Key, Good, Bad). + +%% add/3 + +add(Key, Good, Bad) -> + {[],[]} = {[{Vs,T} || Vs <- Good, + T <- [add(Key, Vs)], + [T] /= [T || {ok,_} <- [T]]], + [{Vs,T} || Vs <- Bad, + T <- [add(Key, Vs)], + [T] /= [T || {error,_} <- [T]]]}. + +add(Key, Vs) -> + T = list_to_tuple([Key | Vs]), + diameter:add_transport(make_ref(), {connect, [T]}). + +%% start/3 + +start(Key, Good, Bad) -> + {[],[]} = {[{Vs,T} || Vs <- Good, + T <- [start(Key, Vs)], + T /= ok], + [{Vs,T} || Vs <- Bad, + T <- [start(Key, Vs)], + [T] /= [T || {error,_} <- [T]]]}. + +start(capabilities = K, [Vs]) -> + if is_list(Vs) -> + start(make_ref(), Vs ++ apps(K)); + true -> + {error, Vs} + end; + +start(Key, Vs) + when is_atom(Key) -> + start(make_ref(), [list_to_tuple([Key | Vs]) | apps(Key)]); + +start(SvcName, Opts) -> + try + diameter:start_service(SvcName, Opts) + after + diameter:stop_service(SvcName) + end. + +apps(application) -> + []; +apps(_) -> + [{application, [{dictionary, diameter_gen_base_rfc6733}, + {module, ?MODULE}]}]. diff --git a/lib/diameter/test/diameter_ct.erl b/lib/diameter/test/diameter_ct.erl index 1697287a22..ed2f884681 100644 --- a/lib/diameter/test/diameter_ct.erl +++ b/lib/diameter/test/diameter_ct.erl @@ -53,7 +53,7 @@ info(L0, L1) -> L0, L1), Diff = [T, C, {memory, M}], - ct:pal("INFO: ~p~n", [Diff]). + io:format("INFO: ~p~n", [Diff]). diff(time, T0, T1) -> timer:now_diff(T1, T0); diff --git a/lib/diameter/test/diameter_distribution_SUITE.erl b/lib/diameter/test/diameter_distribution_SUITE.erl new file mode 100644 index 0000000000..f069abbe2f --- /dev/null +++ b/lib/diameter/test/diameter_distribution_SUITE.erl @@ -0,0 +1,378 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Tests of traffic between two Diameter nodes, the client being +%% spread across three Erlang nodes. +%% + +-module(diameter_distribution_SUITE). + +-export([suite/0, + all/0]). + +%% testcases +-export([enslave/1, enslave/0, + ping/1, + start/1, + connect/1, + send_local/1, + send_remote/1, + send_timeout/1, + send_failover/1, + stop/1, stop/0]). + +%% diameter callbacks +-export([peer_up/3, + peer_down/3, + pick_peer/5, + prepare_request/4, + prepare_retransmit/4, + handle_answer/5, + handle_error/5, + handle_request/3]). + +-export([call/1]). + +-include("diameter.hrl"). +-include("diameter_gen_base_rfc6733.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(CLIENT, 'CLIENT'). +-define(SERVER, 'SERVER'). +-define(REALM, "erlang.org"). +-define(DICT, diameter_gen_base_rfc6733). +-define(ADDR, {127,0,0,1}). + +%% Config for diameter:start_service/2. +-define(SERVICE(Host), + [{'Origin-Host', Host ++ [$.|?REALM]}, + {'Origin-Realm', ?REALM}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Auth-Application-Id', [?DICT:id()]}, + {'Origin-State-Id', origin()}, + {share_peers, peers()}, + {use_shared_peers, peers()}, + {restrict_connections, false}, + {sequence, fun sequence/0}, + {application, [{dictionary, ?DICT}, + {module, ?MODULE}, + {request_errors, callback}, + {answer_errors, callback}]}]). + +-define(SUCCESS, 2001). +-define(BUSY, 3004). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). +-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). +-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). + +-define(L, atom_to_list). +-define(A, list_to_atom). + +%% The order here is significant and causes the server to listen +%% before the clients connect. +-define(NODES, [{server, ?SERVER}, + {client0, ?CLIENT}, + {client1, ?CLIENT}, + {client2, ?CLIENT}]). + +%% Options to ct_slave:start/2. +-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout, + init_timeout, + start_timeout]]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [enslave, + ping, + start, + connect, + send_local, + send_remote, + send_timeout, + send_failover, + stop]. + +%% =========================================================================== +%% start/stop testcases + +%% enslave/1 +%% +%% Start four slave nodes, one to implement a Diameter server, +%% two three to implement a client. + +enslave() -> + [{timetrap, {seconds, 30*length(?NODES)}}]. + +enslave(Config) -> + Here = filename:dirname(code:which(?MODULE)), + Ebin = filename:join([Here, "..", "ebin"]), + Dirs = [Here, Ebin], + Nodes = [{N,S} || {M,S} <- ?NODES, N <- [slave(M, Dirs)]], + ?util:write_priv(Config, nodes, [{N,S} || {{N,ok},S} <- Nodes]), + [] = [{T,S} || {{_,E} = T, S} <- Nodes, E /= ok]. + +slave(Name, Dirs) -> + add_pathsa(Dirs, ct_slave:start(Name, ?TIMEOUTS)). + +add_pathsa(Dirs, {ok, Node}) -> + {Node, rpc:call(Node, code, add_pathsa, [Dirs])}; +add_pathsa(_, No) -> + {No, error}. + +%% ping/1 +%% +%% Ensure the client nodes are connected since the sharing of +%% transports is only between connected nodes. + +ping({?SERVER, _Nodes}) -> + []; + +ping({?CLIENT, Nodes}) -> + [N || {N,_} <- Nodes, + node() /= N, + pang <- [net_adm:ping(N)]]; + +ping(Config) -> + Nodes = ?util:read_priv(Config, nodes), + [] = [{N,RC} || {N,S} <- Nodes, + RC <- [rpc:call(N, ?MODULE, ping, [{S, Nodes}])], + RC /= []]. + +%% start/1 +%% +%% Start diameter services. + +start(SvcName) + when is_atom(SvcName) -> + ok = diameter:start(), + ok = diameter:start_service(SvcName, ?SERVICE((?L(SvcName)))); + +start(Config) -> + Nodes = ?util:read_priv(Config, nodes), + [] = [{N,RC} || {N,S} <- Nodes, + RC <- [rpc:call(N, ?MODULE, start, [S])], + RC /= ok]. + +sequence() -> + sequence(sname()). + +sequence(server) -> + {0,32}; +sequence(Client) -> + "client" ++ N = ?L(Client), + {list_to_integer(N), 30}. + +origin() -> + origin(sname()). + +origin(server) -> + 99; +origin(Client) -> + "client" ++ N = ?L(Client), + list_to_integer(N). + +peers() -> + peers(sname()). + +peers(server) -> true; +peers(client0) -> [node() | nodes()]; +peers(client1) -> fun erlang:nodes/0; +peers(client2) -> nodes(). + +%% connect/1 +%% +%% Establish one connection to the server from each of the client +%% nodes. + +connect({?SERVER, Config}) -> + ?util:write_priv(Config, lref, {node(), ?util:listen(?SERVER, tcp)}), + ok; + +connect({?CLIENT, Config}) -> + ?util:connect(?CLIENT, tcp, ?util:read_priv(Config, lref)), + ok; + +connect(Config) -> + Nodes = ?util:read_priv(Config, nodes), + [] = [{N,RC} || {N,S} <- Nodes, + RC <- [rpc:call(N, ?MODULE, connect, [{S,Config}])], + RC /= ok]. + +%% stop/1 +%% +%% Stop the slave nodes. + +stop() -> + [{timetrap, {seconds, 30*length(?NODES)}}]. + +stop(_Config) -> + [] = [{N,E} || {N,_} <- ?NODES, + {error, _, _} = E <- [ct_slave:stop(N)]]. + +%% =========================================================================== +%% traffic testcases + +%% send_local/1 +%% +%% Send a request from the first client node, using a the local +%% transport. + +send_local(Config) -> + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = send(Config, local, str(?LOGOUT)). + +%% send_remote/1 +%% +%% Send a request from the first client node, using a transport on the +%% another node. + +send_remote(Config) -> + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = send(Config, remote, str(?LOGOUT)). + +%% send_timeout/1 +%% +%% Send a request that the server discards. + +send_timeout(Config) -> + {error, timeout} = send(Config, remote, str(?TIMEOUT)). + +%% send_failover/1 +%% +%% Send a request that causes the server to remote transports down. + +send_failover(Config) -> + #'diameter_base_answer-message'{'Result-Code' = ?BUSY} + = send(Config, remote, str(?MOVED)). + +%% =========================================================================== + +str(Cause) -> + #diameter_base_STR{'Destination-Realm' = ?REALM, + 'Auth-Application-Id' = ?DICT:id(), + 'Termination-Cause' = Cause}. + +%% send/2 + +send(Config, Where, Req) -> + [_, {Node, _} | _] = ?util:read_priv(Config, nodes) , + rpc:call(Node, ?MODULE, call, [{Where, Req}]). + +%% call/1 + +call({Where, Req}) -> + diameter:call(?CLIENT, ?DICT, Req, [{extra, [{Where, sname()}]}]). + +%% sname/0 + +sname() -> + ?A(hd(string:tokens(?L(node()), "@"))). + +%% =========================================================================== +%% diameter callbacks + +%% peer_up/3 + +peer_up(_SvcName, _Peer, State) -> + State. + +%% peer_down/3 + +peer_down(_SvcName, _Peer, State) -> + State. + +%% pick_peer/4 + +pick_peer([LP], [_, _], ?CLIENT, _State, {local, client0}) -> + {ok, LP}; + +pick_peer([_], [RP | _], ?CLIENT, _State, {remote, client0}) -> + {ok, RP}; + +pick_peer([LP], [], ?CLIENT, _State, {remote, client0}) -> + {ok, LP}. + +%% prepare_request/4 + +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {_, client0}) -> + #diameter_packet{msg = Req} + = Pkt, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}} + = Caps, + {send, Req#diameter_base_STR{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = diameter:session_id(OH)}}. + +prepare_retransmit(Pkt, ?CLIENT, _, {_, client0}) -> + #diameter_packet{msg = #diameter_base_STR{'Termination-Cause' = ?MOVED}} + = Pkt, %% assert + {send, Pkt}. + +%% handle_answer/5 + +handle_answer(Pkt, _Req, ?CLIENT, _Peer, {_, client0}) -> + #diameter_packet{msg = Rec, errors = []} = Pkt, + Rec. + +%% handle_error/5 + +handle_error(Reason, _Req, ?CLIENT, _Peer, {_, client0}) -> + {error, Reason}. + +%% handle_request/3 + +handle_request(Pkt, ?SERVER, Peer) -> + server = sname(), %% assert + #diameter_packet{msg = Req} + = Pkt, + request(Req, Peer). + +request(#diameter_base_STR{'Termination-Cause' = ?TIMEOUT}, _) -> + discard; + +request(#diameter_base_STR{'Termination-Cause' = ?MOVED}, Peer) -> + {TPid, #diameter_caps{origin_state_id = {_, [N]}}} = Peer, + fail(N, TPid); + +request(#diameter_base_STR{'Session-Id' = SId}, {_, Caps}) -> + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}} + = Caps, + {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, + 'Session-Id' = SId, + 'Origin-Host' = OH, + 'Origin-Realm' = OR}}. + +fail(0, _) -> %% sent from the originating node ... + {protocol_error, ?BUSY}; + +fail(_, TPid) -> %% ... or through a remote node: force failover + exit(TPid, kill), + discard. diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl index 18bdcb1f54..94b4967921 100644 --- a/lib/diameter/test/diameter_event_SUITE.erl +++ b/lib/diameter/test/diameter_event_SUITE.erl @@ -100,7 +100,7 @@ start_server(Config) -> ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER, [?DICT_COMMON])), LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx_cb/2}, {capx_timeout, ?SERVER_CAPX_TMO}]), - [PortNr] = ?util:lport(tcp, LRef, 20), + [PortNr] = ?util:lport(tcp, LRef), ?util:write_priv(Config, portnr, PortNr), start = event(?SERVER). diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl new file mode 100644 index 0000000000..1954bc319b --- /dev/null +++ b/lib/diameter/test/diameter_examples_SUITE.erl @@ -0,0 +1,359 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Test example code under ../examples/code. +%% + +-module(diameter_examples_SUITE). + +-export([suite/0, + all/0]). + +%% testcases +-export([dict/1, dict/0, + code/1, + slave/1, slave/0, + enslave/1, + start/1, + traffic/1, + stop/1]). + +-export([install/1, + call/1]). + +-include("diameter.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +%% The order here is significant and causes the server to listen +%% before the clients connect. +-define(NODES, [compile, server, client]). + +%% Options to ct_slave:start/2. +-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout, + init_timeout, + start_timeout]]). + +%% @inherits dependencies between example dictionaries. This is needed +%% in order compile them in the right order. Can't compile to erl to +%% find out since @inherits is a beam dependency. +-define(INHERITS, [{rfc4006_cc, [rfc4005_nas]}, + {rfc4072_eap, [rfc4005_nas]}, + {rfc4740_sip, [rfc4590_digest]}]). + +%% Common dictionaries to inherit from examples. +-define(DICT0, [rfc3588_base, rfc6733_base]). + +%% =========================================================================== + +suite() -> + [{timetrap, {minutes, 2}}]. + +all() -> + [dict, + code, + slave, + enslave, + start, + traffic, + stop]. + +%% =========================================================================== +%% dict/1 +%% +%% Compile example dictionaries in examples/dict. + +dict() -> + [{timetrap, {minutes, 10}}]. + +dict(_Config) -> + Dirs = [filename:join(H ++ ["examples", "dict"]) + || H <- [[code:lib_dir(diameter)], [here(), ".."]]], + [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia")), + D <- ?DICT0, + RC <- [make(F,D)], + RC /= ok]. + +sort([{_,_} | _] = Files) -> + lists:sort(fun({A,_},{B,_}) -> + sort([filename:rootname(F) || F <- [A,B]]) + end, + Files); + +sort([A,B] = L) -> + [DA,DB] = [dep([D],[]) || D <- L], + case {[A] -- DB, [B] -- DA} of + {[], [_]} -> %% B depends on A + true; + {[_], []} -> %% A depends on B + false; + {[_],[_]} -> %% or not + length(DA) < length(DB) + end. + +%% Recursively accumulate inherited dictionaries. +dep([D|Rest], Acc) -> + dep(dep(D), Rest, Acc); +dep([], Acc) -> + Acc. + +dep([{Dict, _} | T], Rest, Acc) -> + dep(T, [Dict | Rest], [Dict | Acc]); +dep([], Rest, Acc) -> + dep(Rest, Acc). + +make(Path, Dict0) + when is_atom(Dict0) -> + make(Path, atom_to_list(Dict0)); + +make(Path, Dict0) -> + Dict = filename:rootname(filename:basename(Path)), + {Mod, Pre} = make_name(Dict), + {"diameter_gen_base" ++ Suf = Mod0, _} = make_name(Dict0), + Name = Mod ++ Suf, + try + ok = to_erl(Path, [{name, Name}, + {prefix, Pre}, + {inherits, "rfc3588_base/" ++ Mod0} + | [{inherits, D ++ "/" ++ M ++ Suf} + || {D,M} <- dep(Dict)]]), + ok = to_beam(Name) + catch + throw: {_,_} = E -> + E + end. + +to_erl(File, Opts) -> + case diameter_make:codec(File, Opts) of + ok -> + ok; + No -> + throw({make, No}) + end. + +to_beam(Name) -> + case compile:file(Name ++ ".erl", [return]) of + {ok, _, _} -> + ok; + No -> + throw({compile, No}) + end. + +dep(Dict) -> + case lists:keyfind(list_to_atom(Dict), 1, ?INHERITS) of + {_, Is} -> + lists:map(fun inherits/1, Is); + false -> + [] + end. + +inherits(Dict) + when is_atom(Dict) -> + inherits(atom_to_list(Dict)); + +inherits(Dict) -> + {Name, _} = make_name(Dict), + {Dict, Name}. + +make_name(Dict) -> + {R, [$_|N]} = lists:splitwith(fun(C) -> C /= $_ end, Dict), + {string:join(["diameter_gen", N, R], "_"), "diameter_" ++ N}. + +%% =========================================================================== +%% code/1 +%% +%% Compile example code under examples/code. + +code(Config) -> + Node = slave(hd(?NODES), here()), + [] = rpc:call(Node, + ?MODULE, + install, + [proplists:get_value(priv_dir, Config)]). + +%% Compile on another node since the code path may be modified. +install(PrivDir) -> + Top = install(here(), PrivDir), + Src = filename:join([Top, "examples", "code"]), + Files = find_files([Src], ".*\\.erl"), + [] = [{F,E} || {_,F} <- Files, + {error, _, _} = E <- [compile:file(F, [warnings_as_errors, + return_errors])]]. + +%% Copy include files into a temporary directory and adjust the code +%% path in order for example code to be able to include them with +%% include_lib. This is really only required when running in the reop +%% since generated includes, that the example code wants to +%% include_lib, are under src/gen and there's no way to get get the +%% preprocessor to find these otherwise. Generated hrls are only be +%% under include in an installation. ("Installing" them locally is +%% anathema.) +install(Dir, PrivDir) -> + %% Remove the path added by slave/1 (needed for the rpc:call/4 in + %% compile/1 to find ?MODULE) so the call to code:lib_dir/2 below + %% returns the installed path. + [Ebin | _] = code:get_path(), + true = code:del_path(Ebin), + Top = top(Dir, code:lib_dir(diameter)), + + %% Create a new diameter/include in priv_dir. Copy all includes + %% there, from below ../include and ../src/gen if they exist (in + %% the repo). + Tmp = filename:join([PrivDir, "diameter"]), + TmpInc = filename:join([PrivDir, "diameter", "include"]), + TmpEbin = filename:join([PrivDir, "diameter", "ebin"]), + [] = [{T,E} || T <- [Tmp, TmpInc, TmpEbin], + {error, E} <- [file:make_dir(T)]], + + Inc = filename:join([Top, "include"]), + Gen = filename:join([Top, "src", "gen"]), + Files = find_files([Inc, Gen], ".*\\.hrl"), + [] = [{F,E} || {_,F} <- Files, + B <- [filename:basename(F)], + D <- [filename:join([TmpInc, B])], + {error, E} <- [file:copy(F,D)]], + + %% Prepend the created directory just so that code:lib_dir/1 finds + %% it when compile:file/2 tries to resolve include_lib. + true = code:add_patha(TmpEbin), + Tmp = code:lib_dir(diameter), %% assert + %% Return the top directory containing examples/code. + Top. + +find_files(Dirs, RE) -> + lists:foldl(fun(D,A) -> fold_files(D, RE, A) end, + orddict:new(), + Dirs). + +fold_files(Dir, RE, Acc) -> + filelib:fold_files(Dir, RE, false, fun store/2, Acc). + +store(Path, Dict) -> + orddict:store(filename:basename(Path), Path, Dict). + +%% =========================================================================== + +%% slave/1 +%% +%% Return how long slave start/stop is taking since it seems to be +%% ridiculously long on some hosts. + +slave() -> + [{timetrap, {minutes, 10}}]. + +slave(_) -> + T0 = now(), + {ok, Node} = ct_slave:start(?MODULE, ?TIMEOUTS), + T1 = now(), + T2 = rpc:call(Node, erlang, now, []), + {ok, Node} = ct_slave:stop(?MODULE), + now_diff([T0, T1, T2, now()]). + +now_diff([T1,T2|_] = Ts) -> + [timer:now_diff(T2,T1) | now_diff(tl(Ts))]; +now_diff(_) -> + []. + +%% =========================================================================== + +%% enslave/1 +%% +%% Start two nodes: one for the server, one for the client. + +enslave(Config) -> + Dir = here(), + Nodes = [{N, slave(N, Dir)} || N <- tl(?NODES)], + ?util:write_priv(Config, nodes, Nodes). + +slave(Name, Dir) -> + {ok, Node} = ct_slave:start(Name, ?TIMEOUTS), + ok = rpc:call(Node, + code, + add_pathsa, + [[Dir, filename:join([Dir, "..", "ebin"])]]), + Node. + +here() -> + filename:dirname(code:which(?MODULE)). + +top(Dir, LibDir) -> + File = filename:join([Dir, "depend.sed"]), %% only in the repo + case filelib:is_regular(File) of + true -> filename:join([Dir, ".."]); + false -> LibDir + end. + +%% start/1 + +start(server) -> + ok = diameter:start(), + ok = server:start(), + {ok, Ref} = server:listen(tcp), + [_] = ?util:lport(tcp, Ref), + ok; + +start(client) -> + ok = diameter:start(), + true = diameter:subscribe(client), + ok = client:start(), + {ok, Ref} = client:connect(tcp), + receive #diameter_event{info = {up, Ref, _, _, _}} -> ok end; + +start(Config) -> + Nodes = ?util:read_priv(Config, nodes), + [] = [RC || {T,N} <- Nodes, + RC <- [rpc:call(N, ?MODULE, start, [T])], + RC /= ok]. + +%% traffic/1 +%% +%% Send successful messages from client to server. + +traffic(server) -> + ok; + +traffic(client) -> + {_, MRef} = spawn_monitor(fun() -> call(100) end), + receive {'DOWN', MRef, process, _, Reason} -> Reason end; + +traffic(Config) -> + Nodes = ?util:read_priv(Config, nodes), + [] = [RC || {T,N} <- Nodes, + RC <- [rpc:call(N, ?MODULE, traffic, [T])], + RC /= ok]. + +call(0) -> + exit(ok); + +call(N) -> + {ok, _} = client:call(), + call(N-1). + +%% stop/1 + +stop(Name) + when is_atom(Name) -> + {ok, _Node} = ct_slave:stop(Name), + ok; + +stop(_Config) -> + [] = [RC || N <- ?NODES, RC <- [stop(N)], RC /= ok]. diff --git a/lib/diameter/test/diameter_gen_tcp_SUITE.erl b/lib/diameter/test/diameter_gen_tcp_SUITE.erl new file mode 100644 index 0000000000..7e232edb44 --- /dev/null +++ b/lib/diameter/test/diameter_gen_tcp_SUITE.erl @@ -0,0 +1,106 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Some gen_sctp-specific tests demonstrating problems that were +%% encountered during diameter development but have nothing +%% specifically to do with diameter. At least one of them can cause +%% diameter_traffic_SUITE testcases to fail. +%% + +-module(diameter_gen_tcp_SUITE). + +-export([suite/0, + all/0]). + +%% testcases +-export([send_long/1]). + +-define(LOOPBACK, {127,0,0,1}). +-define(GEN_OPTS, [binary, {active, true}, {ip, ?LOOPBACK}]). + +%% =========================================================================== + +suite() -> + [{timetrap, {minutes, 2}}]. + +all() -> + [send_long]. + +%% =========================================================================== + +%% send_long/1 +%% +%% Test that a long message is received. + +send_long(_) -> + {Sock, SendF} = connection(), + B = list_to_binary(lists:duplicate(1 bsl 20, $X)), + ok = SendF(B), + B = recv(Sock, size(B), []). + +recv(_, 0, Acc) -> + list_to_binary(lists:reverse(Acc)); +recv(Sock, N, Acc) -> + receive + {tcp, Sock, Bin} -> + recv(Sock, N - size(Bin), [Bin | Acc]); + T -> + {T, Acc} + end. + +%% connection/0 + +connection() -> + {ok, LSock} = gen_tcp:listen(0, ?GEN_OPTS), + {ok, PortNr} = inet:port(LSock), + LPid = self(), + {Pid, MRef} = spawn_monitor(fun() -> connect(PortNr, LPid) end), + {ok, Sock} = gen_tcp:accept(LSock), + receive + {Pid, F} -> + {Sock, F}; + {'DOWN', MRef, process, _, _} = T -> + T + end. + +%% connect/2 + +connect(PortNr, LPid) -> + {ok, Sock} = gen_tcp:connect(?LOOPBACK, PortNr, ?GEN_OPTS), + LPid ! {self(), fun(B) -> send(Sock, B) end}, + down(LPid). + +%% down/1 + +down(Pid) + when is_pid(Pid) -> + down(erlang:monitor(process, Pid)); + +down(MRef) -> + receive {'DOWN', MRef, process, _, Reason} -> Reason end. + +%% send/2 +%% +%% Send from a spawned process just to avoid sending from the +%% receiving process, in case it's significant. + +send(Sock, Bin) -> + {_, MRef} = spawn_monitor(fun() -> exit(gen_tcp:send(Sock, Bin)) end), + down(MRef). diff --git a/lib/diameter/test/diameter_stats_SUITE.erl b/lib/diameter/test/diameter_stats_SUITE.erl index af52afb59c..76ff764671 100644 --- a/lib/diameter/test/diameter_stats_SUITE.erl +++ b/lib/diameter/test/diameter_stats_SUITE.erl @@ -33,6 +33,7 @@ -export([reg/1, incr/1, read/1, + sum/1, flush/1]). -define(stat, diameter_stats). @@ -53,6 +54,7 @@ tc() -> [reg, incr, read, + sum, flush]. init_per_suite(Config) -> @@ -98,6 +100,23 @@ read(_) -> [] = ?stat:read([make_ref()]), ?stat:flush([self(), Ref, make_ref()]). +sum(_) -> + Ref = make_ref(), + C1 = {a,b}, + C2 = {b,a}, + true = ?stat:reg(Ref), + 1 = ?stat:incr(C1), + 1 = ?stat:incr(C2), + 2 = ?stat:incr(C2), + 7 = ?stat:incr(C1, Ref, 7), + [{Ref, [{C1,8}, {C2,2}]}] + = ?stat:sum([Ref, make_ref()]), + Self = self(), + [{Self, [{C1,1}, {C2,2}]}] + = ?stat:sum([self()]), + [{Ref, [{C1,7}]}, {Self, [{C1,1}, {C2,2}]}] + = lists:sort(?stat:flush([self(), Ref])). + flush(_) -> Ref = make_ref(), Ctr = '_', diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl index 77194a0f56..55565692ec 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_SUITE.erl @@ -181,7 +181,7 @@ start_diameter(_Config) -> ok = diameter:start(). make_certs() -> - [{timetrap, {seconds, 30}}]. + [{timetrap, {minutes, 2}}]. make_certs(Config) -> Dir = proplists:get_value(priv_dir, Config), @@ -302,9 +302,7 @@ set([H|T], Vs) -> disconnect({{LRef, _PortNr}, CRef}) -> ok = diameter:remove_transport(?CLIENT, CRef), - ok = receive #diameter_event{info = {down, LRef, _, _}} -> ok - after 2000 -> false - end. + receive #diameter_event{info = {down, LRef, _, _}} -> ok end. realm(Host) -> tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). @@ -343,7 +341,7 @@ join(Strs) -> server(Host, {Caps, Opts}) -> ok = diameter:start_service(Host, ?SERVICE(Host, ?DICT_COMMON)), {ok, LRef} = diameter:add_transport(Host, ?LISTEN(Caps, Opts)), - {LRef, hd([_] = ?util:lport(tcp, LRef, 20))}. + {LRef, hd([_] = ?util:lport(tcp, LRef))}. sopts(?SERVER1, Dir) -> {inband_security([?TLS]), @@ -365,13 +363,11 @@ ssl([{ssl_options = T, Opts}]) -> connect(Host, {_LRef, PortNr}, {Caps, Opts}) -> {ok, Ref} = diameter:add_transport(Host, ?CONNECT(PortNr, Caps, Opts)), - ok = receive - #diameter_event{service = Host, - info = {up, Ref, _, _, #diameter_packet{}}} -> - ok - after 2000 -> - false - end, + receive + #diameter_event{service = Host, + info = {up, Ref, _, _, #diameter_packet{}}} -> + ok + end, Ref. copts(S, Opts) diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 781ed234cc..38bdf55af8 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -49,9 +49,12 @@ send_unsupported_app/1, send_error_bit/1, send_unsupported_version/1, - send_invalid_avp_bits/1, + send_long_avp_length/1, + send_short_avp_length/1, + send_zero_avp_length/1, send_invalid_avp_length/1, send_invalid_reject/1, + send_unrecognized_mandatory/1, send_long/1, send_nopeer/1, send_noapp/1, @@ -268,9 +271,12 @@ tc() -> send_unsupported_app, send_error_bit, send_unsupported_version, - send_invalid_avp_bits, + send_long_avp_length, + send_short_avp_length, + send_zero_avp_length, send_invalid_avp_length, send_invalid_reject, + send_unrecognized_mandatory, send_long, send_nopeer, send_noapp, @@ -405,7 +411,7 @@ send_eval(Config) -> send_bad_answer(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 2}], - {error, timeout} = call(Config, Req). + {timeout, _} = call(Config, Req). %% Send an ACR that the server callback answers explicitly with a %% protocol error. @@ -416,13 +422,14 @@ send_protocol_error(Config) -> ?answer_message(?TOO_BUSY) = call(Config, Req). -%% Send an ASR with an arbitrary AVP and expect success and the same -%% AVP in the reply. +%% Send an ASR with an arbitrary non-mandatory AVP and expect success +%% and the same AVP in the reply. send_arbitrary(Config) -> - Req = ['ASR', {'AVP', [#diameter_avp{name = 'Class', value = "XXX"}]}], + Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name', + value = "XXX"}]}], ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), - {'AVP', [#diameter_avp{name = 'Class', + {'AVP', [#diameter_avp{name = 'Product-Name', value = "XXX"}]} = lists:last(Avps). @@ -455,7 +462,7 @@ send_unknown_mandatory(Config) -> %% Send an STR that the server ignores. send_noreply(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - {error, timeout} = call(Config, Req). + {timeout, _} = call(Config, Req). %% Send an unsupported command and expect 3001. send_unsupported(Config) -> @@ -481,19 +488,33 @@ send_unsupported_version(Config) -> ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _] = call(Config, Req). -%% Send a request containing an incorrect AVP length. -send_invalid_avp_bits(Config) -> - Req = ['STR', {'Termination-Cause', ?LOGOUT}], +%% Send a request containing an AVP length > data size. +send_long_avp_length(Config) -> + send_invalid_avp_length(Config). - ?answer_message(?INVALID_AVP_BITS) - = call(Config, Req). +%% Send a request containing an AVP length < data size. +send_short_avp_length(Config) -> + send_invalid_avp_length(Config). + +%% Send a request containing an AVP whose advertised length is < 8. +send_zero_avp_length(Config) -> + send_invalid_avp_length(Config). %% Send a request containing an AVP length that doesn't match the %% AVP's type. send_invalid_avp_length(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - ['STA', _SessionId, {'Result-Code', ?INVALID_AVP_LENGTH} | _] + ['STA', _SessionId, + {'Result-Code', ?INVALID_AVP_LENGTH}, + _OriginHost, + _OriginRealm, + _UserName, + _Class, + _ErrorMessage, + _ErrorReportingHost, + {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]} + | _] = call(Config, Req). %% Send a request containing 5xxx errors that the server rejects with @@ -504,6 +525,14 @@ send_invalid_reject(Config) -> ?answer_message(?TOO_BUSY) = call(Config, Req). +%% Send an STR containing a known AVP, but one that's not allowed and +%% sets the M-bit. +send_unrecognized_mandatory(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + + ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _] + = call(Config, Req). + %% Send something long that will be fragmented by TCP. send_long(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, @@ -552,7 +581,7 @@ send_all_2(Config) -> %% Timeout before the server manages an answer. send_timeout(Config) -> Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_ONLY}], - {error, timeout} = call(Config, Req, [{timeout, 1000}]). + {timeout, _} = call(Config, Req, [{timeout, 1000}]). %% Explicitly answer with an answer-message and ensure that we %% received the Session-Id. @@ -560,7 +589,7 @@ send_error(Config) -> Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}], ?answer_message(SId, ?TOO_BUSY) = call(Config, Req), - undefined /= SId. + true = undefined /= SId. %% Send a request with the detached option and receive it as a message %% from handle_answer instead. @@ -568,7 +597,7 @@ send_detach(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], Ref = make_ref(), ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]), - Ans = receive {Ref, T} -> T after 2000 -> false end, + Ans = receive {Ref, T} -> T end, ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = Ans. @@ -683,7 +712,7 @@ call(Config, Req, Opts) -> diameter:call(?CLIENT, dict(Req, Dict0), msg(Req, ReqEncoding, Dict0), - [{extra, [Name, Group]} | Opts]). + [{extra, [{Name, Group}, now()]} | Opts]). origin({A,C}) -> 2*codec(A) + container(C); @@ -767,14 +796,14 @@ peer_down(_SvcName, _Peer, State) -> %% pick_peer/6-7 -pick_peer(Peers, _, ?CLIENT, _State, Name, Group) +pick_peer(Peers, _, ?CLIENT, _State, {Name, Group}, _) when Name /= send_detach -> find(Group, Peers). -pick_peer(_Peers, _, ?CLIENT, _State, send_nopeer, _, ?EXTRA) -> +pick_peer(_Peers, _, ?CLIENT, _State, {send_nopeer, _}, _, ?EXTRA) -> false; -pick_peer(Peers, _, ?CLIENT, _State, send_detach, Group, {_,_}) -> +pick_peer(Peers, _, ?CLIENT, _State, {send_detach, Group}, _, {_,_}) -> find(Group, Peers). find(#group{server_encoding = A, server_container = C}, Peers) -> @@ -789,13 +818,13 @@ id(Id, {Pid, _Caps}) -> %% prepare_request/5-6 -prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, send_discard, _) -> +prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, {send_discard, _}, _) -> {discard, unprepared}; -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, Group) -> +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {Name, Group}, _) -> {send, prepare(Pkt, Caps, Name, Group)}. -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, Group, _) -> +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {send_detach, Group}, _, _) -> {eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}. log(#diameter_packet{bin = Bin} = P, T) @@ -804,45 +833,63 @@ log(#diameter_packet{bin = Bin} = P, T) %% prepare/4 -prepare(Pkt, Caps, send_invalid_avp_bits, #group{client_dict0 = Dict0} - = Group) -> +prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) + when N == send_long_avp_length; + N == send_short_avp_length; + N == send_zero_avp_length -> Req = prepare(Pkt, Caps, Group), - %% Last AVP in our STR is Termination-Cause of type Unsigned32: - %% set its length improperly. + %% Second last AVP in our STR is Auth-Application-Id of type + %% Unsigned32: set AVP Length to a value other than 12 and place + %% it last in the message (so as not to mess with Termination-Cause). #diameter_packet{header = #diameter_header{length = L}, bin = B} = E = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), - Offset = L - 7, %% to AVP Length - <<H:Offset/binary, 12:24/integer, T:4/binary>> = B, - E#diameter_packet{bin = <<H/binary, 13:24/integer, T/binary>>}; + Offset = L - 24, %% to Auth-Application-Id + <<H:Offset/binary, + Hdr:5/binary, 12:24, Data:4/binary, + T:12/binary>> + = B, + AL = case N of + send_long_avp_length -> 13; + send_short_avp_length -> 11; + send_zero_avp_length -> 0 + end, + E#diameter_packet{bin = <<H/binary, + T/binary, + Hdr/binary, AL:24, Data/binary>>}; prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) when N == send_invalid_avp_length; N == send_invalid_reject -> Req = prepare(Pkt, Caps, Group), %% Second last AVP in our STR is Auth-Application-Id of type - %% Unsigned32: Send a value of length 8. + %% Unsigned32: send data of length 8. #diameter_packet{header = #diameter_header{length = L}, bin = B0} = E = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), Offset = L - 7 - 12, %% to AVP Length - <<H0:Offset/binary, 12:24/integer, T:16/binary>> = B0, - <<V, L:24/integer, H/binary>> = H0, %% assert - E#diameter_packet{bin = <<V, - (L+4):24/integer, - H/binary, - 16:24/integer, - 0:32/integer, - T/binary>>}; + <<H0:Offset/binary, 12:24, T:16/binary>> = B0, + <<V, L:24, H/binary>> = H0, %% assert + E#diameter_packet{bin = <<V, (L+4):24, H/binary, 16:24, 0:32, T/binary>>}; + +prepare(Pkt, Caps, send_unrecognized_mandatory, #group{client_dict0 = Dict0} + = Group) -> + Req = prepare(Pkt, Caps, Group), + #diameter_packet{bin = <<V, Len:24, T/binary>>} + = E + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), + {Code, Flags, undefined} = Dict0:avp_header('Proxy-State'), + Avp = <<Code:32, Flags, 8:24>>, + E#diameter_packet{bin = <<V, (Len+8):24, T/binary, Avp/binary>>}; prepare(Pkt, Caps, send_unsupported, #group{client_dict0 = Dict0} = Group) -> Req = prepare(Pkt, Caps, Group), #diameter_packet{bin = <<H:5/binary, _CmdCode:3/binary, T/binary>>} = E = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), - E#diameter_packet{bin = <<H/binary, 42:24/integer, T/binary>>}; + E#diameter_packet{bin = <<H/binary, 42:24, T/binary>>}; prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0} = Group) -> @@ -850,7 +897,7 @@ prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0} #diameter_packet{bin = <<H:8/binary, _ApplId:4/binary, T/binary>>} = E = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), - E#diameter_packet{bin = <<H/binary, ?BAD_APP:32/integer, T/binary>>}; + E#diameter_packet{bin = <<H/binary, ?BAD_APP:32, T/binary>>}; prepare(Pkt, Caps, send_error_bit, Group) -> #diameter_packet{header = Hdr} = Pkt, @@ -928,10 +975,10 @@ prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) -> %% handle_answer/6-7 -handle_answer(Pkt, Req, ?CLIENT, Peer, Name, Group) -> +handle_answer(Pkt, Req, ?CLIENT, Peer, {Name, Group}, _) -> answer(Pkt, Req, Peer, Name, Group). -handle_answer(Pkt, Req, ?CLIENT, Peer, send_detach = Name, Group, X) -> +handle_answer(Pkt, Req, ?CLIENT, Peer, {send_detach = Name, Group}, _, X) -> {Pid, Ref} = X, Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}. @@ -944,7 +991,9 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) -> [Dict:rec2msg(R) | Vs]. answer(Rec, [_|_], N) - when N == send_invalid_avp_bits; + when N == send_long_avp_length; + N == send_short_avp_length; + N == send_zero_avp_length; N == send_invalid_avp_length; N == send_invalid_reject -> Rec; @@ -959,7 +1008,11 @@ app(Req, _, Dict0) -> %% handle_error/6 -handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Group) -> +handle_error(timeout = Reason, _Req, ?CLIENT, _Peer, _, Time) -> + Now = now(), + {Reason, {Time, Now, timer:now_diff(Now, Time)}}; + +handle_error(Reason, _Req, ?CLIENT, _Peer, _, _Time) -> {error, Reason}. %% handle_request/3 @@ -1085,7 +1138,6 @@ request(#diameter_base_STR{'Session-Id' = SId}, {'Origin-Host', OH}, {'Origin-Realm', OR}]}; -%% send_error +%% send_error/send_timeout request(#diameter_base_RAR{}, _Caps) -> - receive after 2000 -> ok end, - {protocol_error, ?TOO_BUSY}. + receive after 2000 -> {protocol_error, ?TOO_BUSY} end. diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl index 893b7ba2f9..97f4cec11f 100644 --- a/lib/diameter/test/diameter_transport_SUITE.erl +++ b/lib/diameter/test/diameter_transport_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -36,6 +36,7 @@ tcp_connect/1, sctp_accept/1, sctp_connect/1, + reconnect/1, reconnect/0, stop/1]). -export([accept/1, @@ -54,9 +55,6 @@ -define(RECV(Pat, Ret), receive Pat -> Ret end). -define(RECV(Pat), ?RECV(Pat, now())). -%% Or not. --define(WAIT(Ms), receive after Ms -> now() end). - %% Sockets are opened on the loopback address. -define(ADDR, {127,0,0,1}). @@ -102,7 +100,8 @@ tc() -> [tcp_accept, tcp_connect, sctp_accept, - sctp_connect]. + sctp_connect, + reconnect]. init_per_suite(Config) -> [{sctp, have_sctp()} | Config]. @@ -165,6 +164,90 @@ connect(Prot) -> [] = ?util:run([{?MODULE, [init, X, T]} || X <- [gen_accept, connect]]). %% =========================================================================== +%% reconnect/1 +%% +%% Exercise reconnection behaviour: that a connecting transport +%% doesn't try to establish a new connection until the old one is +%% broken. + +reconnect() -> + [{timetrap, {minutes, 4}}]. + +reconnect({listen, Ref}) -> + SvcName = make_ref(), + ok = start_service(SvcName), + LRef = ?util:listen(SvcName, tcp, [{watchdog_timer, 6000}]), + [_] = diameter_reg:wait({diameter_tcp, listener, {LRef, '_'}}), + true = diameter_reg:add_new({?MODULE, Ref, LRef}), + + %% Wait for partner to request transport death: kill to force the + %% peer to reconnect. + TPid = abort(SvcName, LRef, Ref), + + exit(TPid, kill), + + abort(SvcName, LRef, Ref); + +reconnect({connect, Ref}) -> + SvcName = make_ref(), + true = diameter:subscribe(SvcName), + ok = start_service(SvcName), + [{{_, _, LRef}, Pid}] = diameter_reg:wait({?MODULE, Ref, '_'}), + CRef = ?util:connect(SvcName, tcp, LRef, [{reconnect_timer, 2000}, + {watchdog_timer, 6000}]), + + %% Tell partner to kill transport after seeing that there are no + %% reconnection attempts. + abort(SvcName, Pid, Ref), + + %% Transport does down and is reestablished. + ?RECV(#diameter_event{service = SvcName, info = {down, CRef, _, _}}), + ?RECV(#diameter_event{service = SvcName, info = {reconnect, CRef, _}}), + ?RECV(#diameter_event{service = SvcName, info = {up, CRef, _, _, _}}), + + %% Kill again. + abort(SvcName, Pid, Ref), + + %% Wait for partner to die. + MRef = erlang:monitor(process, Pid), + ?RECV({'DOWN', MRef, process, _, _}); + +reconnect(_) -> + Ref = make_ref(), + [] = ?util:run([{?MODULE, [reconnect, {T, Ref}]} + || T <- [listen, connect]]). + +start_service(SvcName) -> + OH = io_lib:format("~p-~p-~p", tuple_to_list(now())), + Opts = [{application, [{dictionary, diameter_gen_base_rfc6733}, + {module, diameter_callback}]}, + {'Origin-Host', OH}, + {'Origin-Realm', OH ++ ".org"}, + {'Vendor-Id', 0}, + {'Product-Name', "x"}, + {'Auth-Application-Id', [0]}], + diameter:start_service(SvcName, Opts). + +abort(SvcName, Pid, Ref) + when is_pid(Pid) -> + receive + #diameter_event{service = SvcName, info = {reconnect, _, _}} = E -> + erlang:error(E) + after 45000 -> + ok + end, + Pid ! {abort, Ref}; + +abort(SvcName, LRef, Ref) + when is_reference(LRef) -> + ?RECV({abort, Ref}), + [[{ref, LRef}, {type, listen}, {options, _}, {accept, [_,_] = Ts} | _]] + %% assert on two accepting + = diameter:service_info(SvcName, transport), + [TPid] = [P || [{watchdog, {_,_,okay}}, {peer, {P,_}} | _] <- Ts], + TPid. + +%% =========================================================================== %% =========================================================================== %% have_sctp/0 @@ -209,7 +292,7 @@ init(accept, {Prot, Ref}) -> init(gen_connect, {Prot, Ref}) -> %% Lookup the peer's listening socket. - [PortNr] = ?util:lport(Prot, Ref, 20), + [PortNr] = ?util:lport(Prot, Ref), %% Connect, send a message and receive it back. {ok, Sock} = gen_connect(Prot, PortNr), @@ -230,7 +313,8 @@ init(gen_accept, {Prot, Ref}) -> init(connect, {Prot, Ref}) -> %% Lookup the peer's listening socket. - [{?TEST_LISTENER(_, PortNr), _}] = match(?TEST_LISTENER(Ref, '_')), + [{?TEST_LISTENER(_, PortNr), _}] + = diameter_reg:wait(?TEST_LISTENER(Ref, '_')), %% Start a connecting transport and receive notification of %% the connection. @@ -246,18 +330,6 @@ init(connect, {Prot, Ref}) -> MRef = erlang:monitor(process, TPid), ?RECV({'DOWN', MRef, process, _, _}). -match(Pat) -> - match(Pat, 20). - -match(Pat, T) -> - L = diameter_reg:match(Pat), - if [] /= L orelse 1 == T -> - L; - true -> - ?WAIT(50), - match(Pat, T-1) - end. - bin(sctp, #diameter_packet{bin = Bin}) -> Bin; bin(tcp, Bin) -> @@ -310,22 +382,18 @@ start_connect(tcp, T, Svc, Opts) -> %% start_accept/2 %% %% Start transports sequentially by having each wait for a message -%% from a job in a queue before commencing. Only one transport with -%% a pending accept is started at a time since diameter_sctp currently -%% assumes (and diameter currently implements) this. +%% from a job in a queue before commencing. Only one transport with a +%% pending accept is started at a time since diameter_{tcp,sctp} +%% currently assume (and diameter currently implements) this. start_accept(Prot, Ref) -> Pid = sync(accept, Ref), - - %% Configure the same port number for transports on the same - %% reference. - [PortNr | _] = ?util:lport(Prot, Ref) ++ [0], {Mod, Opts} = tmod(Prot), try {ok, TPid, [?ADDR]} = Mod:start({accept, Ref}, ?SVC([?ADDR]), - [{port, PortNr} | Opts]), + [{port, 0} | Opts]), ?RECV(?TMSG({TPid, connected})), TPid after diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 5af4ad9ba5..aa489fef5f 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -33,7 +33,6 @@ %% diameter-specific -export([lport/2, - lport/3, listen/2, listen/3, connect/3, connect/4, disconnect/4, @@ -251,24 +250,17 @@ path(Config, Name) -> filename:join([Dir, Name]). %% --------------------------------------------------------------------------- -%% lport/2-3 +%% lport/2 %% %% Lookup the port number of a tcp/sctp listening transport. -lport(M, Ref) -> - lport(M, Ref, 1). +lport(M, {Node, Ref}) -> + rpc:call(Node, ?MODULE, lport, [M, Ref]); -lport(M, Ref, Tries) -> - lp(tmod(M), Ref, Tries). - -lp(M, Ref, T) -> - L = [N || {listen, N, _} <- M:ports(Ref)], - if [] /= L orelse T =< 1 -> - L; - true -> - receive after 50 -> ok end, - lp(M, Ref, T-1) - end. +lport(Prot, Ref) -> + Mod = tmod(Prot), + [_] = diameter_reg:wait({'_', listener, {Ref, '_'}}), + [N || {listen, N, _} <- Mod:ports(Ref)]. %% --------------------------------------------------------------------------- %% listen/2-3 @@ -294,7 +286,7 @@ connect(Client, Prot, LRef) -> connect(Client, Prot, LRef, []). connect(Client, Prot, LRef, Opts) -> - [PortNr] = lport(Prot, LRef, 20), + [PortNr] = lport(Prot, LRef), Client = diameter:service_info(Client, name), %% assert true = diameter:subscribe(Client), Ref = add_transport(Client, {connect, opts(Prot, PortNr) ++ Opts}), diff --git a/lib/diameter/test/diameter_watchdog_SUITE.erl b/lib/diameter/test/diameter_watchdog_SUITE.erl index e1e166b834..b6e8730ec2 100644 --- a/lib/diameter/test/diameter_watchdog_SUITE.erl +++ b/lib/diameter/test/diameter_watchdog_SUITE.erl @@ -30,10 +30,14 @@ end_per_suite/1]). %% testcases --export([reopen/1, reopen/4, reopen/7]). +-export([reopen/0, reopen/1, reopen/4, reopen/6, + suspect/1, suspect/4, + okay/1, okay/4]). -export([id/1, %% jitter callback - run1/1]). + run1/1, + abuse/1, + abuse/2]). %% diameter_app callbacks -export([peer_up/3, @@ -64,7 +68,7 @@ {'Host-IP-Address', [?ADDR]}, {'Vendor-Id', 42}, {'Product-Name', "OTP/diameter"}, - {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {'Auth-Application-Id', [0 = ?BASE:id()]}, {application, [{alias, Name}, {dictionary, ?BASE}, {module, ?MODULE}]}]). @@ -72,48 +76,51 @@ %% Watchdog timer as a callback. -define(WD(T), {?MODULE, id, [T]}). -%% Watchdog timers used by the testcases. Note that the short timeout -%% with random jitter is excluded since the reopen/1 isn't smart -%% enough to deal with it: see ONE_WD below. --define(WD_TIMERS, [?WD(6000) - | [F_(T_) || T_ <- [10000, 20000, 30000], - F_ <- [fun(T__) -> T__ end, - fun(T__) -> ?WD(T__) end]]]). +%% Watchdog timers used by the testcases. +-define(WD_TIMERS, [10000, ?WD(10000)]). -%% Watchdog timer of the misbehaving peer. +%% Watchdog timer of the misbehaving node. -define(PEER_WD, 10000). -%% Receive a watchdog event within a specified time. --define(EVENT(T, Tmo), - receive #diameter_event{info = T} -> now() - after Tmo -> ?ERROR({timeout, Tmo}) - end). - -%% Receive an event in a given number of watchdogs, plus or minus -%% half. Note that the call to now_diff assumes left to right -%% evaluation order. --define(EVENT(T, N, WdL, WdH), - [?ERROR({received, _Elapsed_, _LowerBound_, N, WdL}) - || _UpperBound_ <- [(N)*(WdH) + (WdH) div 2], - _Elapsed_ <- [now_diff(now(), ?EVENT(T, _UpperBound_))], - _LowerBound_ <- [(N)*(WdL) - (WdL) div 2], - _Elapsed_ =< _LowerBound_*1000]). - --define(EVENT(T, N, Wd), - ?EVENT(T, N, Wd, Wd)). - -%% A timeout that ensures one watchdog. The ensure only one watchdog +%% A timeout that ensures one watchdog. To ensure only one watchdog %% requires (Wd + 2000) + 1000 < 2*(Wd - 2000) ==> 7000 < Wd for the %% case with random jitter. -define(ONE_WD(Wd), jitter(Wd,2000) + 1000). +-define(INFO(T), #diameter_event{info = T}). + +%% Receive an event message from diameter. +-define(EVENT(T), %% apply to not bind T_ + apply(fun() -> + receive ?INFO(T = T_) -> log_event(T_) end + end, + [])). + +%% Receive a watchdog event. +-define(WD_EVENT(Ref), log_wd(element(4, ?EVENT({watchdog, Ref, _, _, _})))). +-define(WD_EVENT(Ref, Ms), + apply(fun() -> + receive ?INFO({watchdog, Ref, _, T_, _}) -> + log_wd(T_) + after Ms -> + false + end + end, + [])). + +%% Log to make failures identifiable. +-define(LOG(T), ?LOG("~p", [T])). +-define(LOG(F,A), ct:pal("~p: " ++ F, [self() | A])). +-define(WARN(F,A), ct:pal(error, "~p: " ++ F, [self() | A])). %% =========================================================================== suite() -> - [{timetrap, {minutes, 10}}].%% enough for 17 watchdogs @ 30 sec plus jitter + [{timetrap, {seconds, 90}}]. all() -> - [reopen]. + [reopen, + suspect, + okay]. init_per_suite(Config) -> ok = diameter:start(), @@ -129,83 +136,46 @@ end_per_suite(_Config) -> %% Test the watchdog state machine for the required failover, failback %% and reopen behaviour by examining watchdog events. +reopen() -> + [{timetrap, {minutes, 5}}]. %% 20 watchdogs @ 15 sec + reopen(_) -> - [] = run([[reopen, T, Wd, N, M] - || Wd <- ?WD_TIMERS, %% watchdog_timer value - T <- [listen, connect], %% watchdog to test + [] = run([[reopen, T, W, N, M] + || T <- [listen, connect], %% watchdog to test + W <- ?WD_TIMERS, %% watchdog_timer value N <- [0,1,2], %% DWR's to answer before ignoring M <- ['DWR', 'DWA', 'RAA']]). %% how to induce failback -reopen(Type, Wd, N, M) -> - Server = start_service(), - Client = start_service(), - - %% The peer to the transport whose watchdog is tested is given a - %% long watchdog timeout so that it doesn't send DWR of its own. - {Node, Peer} = {{[], Wd}, {[{module, ?MODULE}], ?WD(?PEER_WD)}}, - - {{LH,LW},{CH,CW}} = case Type of - listen -> {Node, Peer}; - connect -> {Peer, Node} - end, - - LO = [{transport_module, diameter_tcp}, - {transport_config, LH ++ [{ip, ?ADDR}, {port, 0}]}, - {watchdog_timer, LW}], - - {ok, LRef} = diameter:add_transport(Server, {listen, LO}), - - [LP] = ?util:lport(tcp, LRef, 20), +reopen(Test, Wd, N, M) -> + %% Publish a ref ensure the connecting transport is added only + %% once events from the listening transport are subscribed to. + Ref = make_ref(), + [] = run([[reopen, T, Test, Ref, Wd, N, M] || T <- [listen, connect]]). - CO = [{transport_module, diameter_tcp}, - {transport_config, CH ++ [{ip, ?ADDR}, {port, 0}, - {raddr, ?ADDR}, {rport, LP}]}, - {watchdog_timer, CW}], +%% reopen/6 - %% Use a temporary process to ensure the connecting transport is - %% added only once events from the listening transport are - %% subscribed to. - Pid = spawn(fun() -> receive _ -> ok end end), +reopen(Type, Test, Ref, Wd, N, M) -> + {SvcName, TRef} = start(Type, Ref, cfg(Type, Test, Wd)), + reopen(Type, Test, SvcName, TRef, Wd, N, M). - [] = run([[reopen, Type, T, LRef, Pid, Wd, N, M] - || T <- [{listen, Server}, {connect, Client, CO}]]). - -%% start_service/1 - -start_service() -> - Name = hostname(), - ok = diameter:start_service(Name, [{monitor, self()} | ?SERVICE(Name)]), - Name. +cfg(Type, Type, Wd) -> + {Wd, [], []}; +cfg(_Type, _Test, _Wd) -> + {?WD(?PEER_WD), [{okay, 0}], [{module, ?MODULE}]}. %% reopen/7 -reopen(Type, {listen = T, SvcName}, Ref, Pid, Wd, N, M) -> - diameter:subscribe(SvcName), - Pid ! ok, - recv(Type, T, SvcName, Ref, Wd, N, M); - -reopen(Type, {connect = T, SvcName, Opts}, _, Pid, Wd, N, M) -> - diameter:subscribe(SvcName), - MRef = erlang:monitor(process, Pid), - receive {'DOWN', MRef, process, _, _} -> ok end, - {ok, Ref} = diameter:add_transport(SvcName, {T, Opts}), - recv(Type, T, SvcName, Ref, Wd, N, M). - -%% recv/7 - %% The watchdog to be tested. -recv(Type, Type, _SvcName, Ref, Wd, N, M) -> +reopen(Type, Type, SvcName, Ref, Wd, N, M) -> + ?LOG("node ~p", [[Type, SvcName, Ref, Wd, N, M]]), + %% Connection should come up immediately as a consequence of %% starting the watchdog process. In the accepting case this %% results in a new watchdog on a transport waiting for a new %% connection. - ?EVENT({watchdog, Ref, _, {initial, okay}, _}, 2000), - ?EVENT({up, Ref, _, _, #diameter_packet{}}, 0), - - %% Low/high watchdog timeouts. - WdL = jitter(Wd, -2000), - WdH = jitter(Wd, 2000), + {initial, okay} = ?WD_EVENT(Ref), + ?EVENT({up, Ref, _, _, #diameter_packet{}}), %% OKAY Timer expires & Failover() %% Pending SetWatchdog() SUSPECT @@ -215,8 +185,13 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% the first unanswered DWR. Knowing the min/max watchdog timeout %% values gives the time interval in which the event is expected. - ?EVENT({watchdog, Ref, _, {okay, suspect}, _}, N+2, WdL, WdH), - ?EVENT({down, Ref, _, _}, 0), + [0,0,0,0] = wd_counts(SvcName), + + {okay, suspect} = ?WD_EVENT(Ref), + ?EVENT({down, Ref, _, _}), + + %% N received DWA's + [_,_,_,N] = wd_counts(SvcName), %% SUSPECT Receive DWA Pending = FALSE %% Failback() @@ -228,8 +203,13 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% The peer sends a message before the expiry of another watchdog %% to induce failback. - ?EVENT({watchdog, Ref, _, {suspect, okay}, _}, WdH + 2000), - ?EVENT({up, Ref, _, _}, 0), + {suspect, okay} = ?WD_EVENT(Ref), + ?EVENT({up, Ref, _, _}), + + %% N+1 sent DWR's, N/N+1 received DWA's + R1 = N+1, + A1 = choose(M == 'DWA', R1, N), + [R1,_,_,A1] = wd_counts(SvcName), %% OKAY Timer expires & SendWatchdog() %% !Pending SetWatchdog() @@ -242,16 +222,19 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% back down after either one or two watchdog expiries, depending %% on whether or not DWA restored the connection. - F = choose(M == 'DWA', 2, 1), - ?EVENT({watchdog, Ref, _, {okay, suspect}, _}, F, WdL, WdH), - ?EVENT({down, Ref, _, _}, 0), + {okay, suspect} = ?WD_EVENT(Ref), + ?EVENT({down, Ref, _, _}), %% SUSPECT Timer expires CloseConnection() %% SetWatchdog() DOWN %% %% Non-response brings the connection down after another timeout. - ?EVENT({watchdog, Ref, _, {suspect, down}, _}, 1, WdL, WdH), + {suspect, down} = ?WD_EVENT(Ref), + + R2 = R1 + choose(M == 'DWA', 1, 0), + A2 = A1, + [R2,_,_,A2] = wd_counts(SvcName), %% DOWN Timer expires AttemptOpen() %% SetWatchdog() DOWN @@ -263,7 +246,7 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% %% The connection is reestablished after another timeout. - recv_reopen(Type, Ref, WdL, WdH), + recv_reopen(Type, Ref), %% REOPEN Receive non-DWA Throwaway() REOPEN %% @@ -281,18 +264,27 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% An exchange of 3 watchdogs (the first directly after %% capabilities exchange) brings the connection back up. - ?EVENT({watchdog, Ref, _, {reopen, okay}, _}, 2, WdL, WdH), - ?EVENT({up, Ref, _, _, #diameter_packet{}}, 0), + {reopen, okay} = ?WD_EVENT(Ref), + ?EVENT({up, Ref, _, _, #diameter_packet{}}), + + %% Three DWR's have been answered. + R3 = R2 + 3, + A3 = A2 + 3, + [R3,_,_,A3] = wd_counts(SvcName), %% Non-response brings it down again. - ?EVENT({watchdog, Ref, _, {okay, suspect}, _}, 2, WdL, WdH), - ?EVENT({down, Ref, _, _}, 0), - ?EVENT({watchdog, Ref, _, {suspect, down}, _}, 1, WdL, WdH), + {okay, suspect} = ?WD_EVENT(Ref), + ?EVENT({down, Ref, _, _}), + {suspect, down} = ?WD_EVENT(Ref), + + R4 = R3 + 1, + A4 = A3, + [R4,_,_,A4] = wd_counts(SvcName), %% Reestablish after another watchdog. - recv_reopen(Type, Ref, WdL, WdH), + recv_reopen(Type, Ref), %% REOPEN Timer expires & NumDWA = -1 %% Pending & SetWatchdog() @@ -305,63 +297,76 @@ recv(Type, Type, _SvcName, Ref, Wd, N, M) -> %% Peer is now ignoring all watchdogs go down again after 2 %% timeouts. - ?EVENT({watchdog, Ref, _, {reopen, down}, _}, 2, WdL, WdH); + {reopen, down} = ?WD_EVENT(Ref); %% The misbehaving peer. -recv(_, Type, SvcName, Ref, Wd, N, M) -> +reopen(Type, _, SvcName, Ref, Wd, N, M) -> + ?LOG("peer ~p", [[Type, SvcName, Ref, Wd, N, M]]), + %% First transport process. - ?EVENT({watchdog, Ref, _, {initial, okay}, _}, 1000), - ?EVENT({up, Ref, _, _, #diameter_packet{}}, 0), - reg(Type, Ref, SvcName, {SvcName, {Wd,N,M}}), - ?EVENT({watchdog, Ref, _, {okay, down}, _}, infinity), + {initial, okay} = ?WD_EVENT(Ref), + ?EVENT({up, Ref, _, _, #diameter_packet{}}), + + reg(Ref, SvcName, {SvcName, {Wd,N,M}}), + + {okay, down} = ?WD_EVENT(Ref), %% Second transport process. - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, infinity), - reg(Type, Ref, SvcName, 3), - ?EVENT({watchdog, Ref, _, {_, down}, _}, infinity), + ?EVENT({watchdog, Ref, _, {_, okay}, _}), + reg(Ref, SvcName, 3), %% answer 3 watchdogs then fall silent + ?EVENT({watchdog, Ref, _, {_, down}, _}), %% Third transport process. - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, infinity), - reg(Type, Ref, SvcName, 0), - ?EVENT({watchdog, Ref, _, {_, down}, _}, infinity), + ?EVENT({watchdog, Ref, _, {_, okay}, _}), + reg(Ref, SvcName, 0), %% disable outgoing DWA + ?EVENT({watchdog, Ref, _, {_, down}, _}), ok. -%% recv_reopen/4 +log_wd({From, To} = T) -> + ?LOG("~p -> ~p", [From, To]), + T. + +log_event(E) -> + T = element(1,E), + T == watchdog orelse ?LOG("~p", [T]), + E. -recv_reopen(connect, Ref, WdL, WdH) -> - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, WdL, WdH), - ?EVENT({reconnect, Ref, _}, 0); +%% recv_reopen/2 -recv_reopen(listen, Ref, _, _) -> - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, ?PEER_WD). +recv_reopen(connect, Ref) -> + {down, reopen} = ?WD_EVENT(Ref), + ?EVENT({reconnect, Ref, _}); -%% reg/4 +recv_reopen(listen, Ref) -> + {_, reopen} = ?WD_EVENT(Ref). + +%% reg/3 %% %% Lookup the pid of the transport process and publish a term for %% send/2 to lookup. -reg(Type, Ref, SvcName, T) -> - TPid = tpid(Type, Ref, diameter:service_info(SvcName, transport)), +reg(TRef, SvcName, T) -> + TPid = tpid(TRef, diameter:service_info(SvcName, transport)), true = diameter_reg:add_new({?MODULE, TPid, T}). -%% tpid/3 - -tpid(connect, Ref, [[{ref, Ref}, - {type, connect}, - {options, _}, - {watchdog, _}, - {peer, _}, - {apps, _}, - {caps, _}, - {port, [{owner, TPid} | _]} - | _]]) -> +%% tpid/2 + +tpid(Ref, [[{ref, Ref}, + {type, connect}, + {options, _}, + {watchdog, _}, + {peer, _}, + {apps, _}, + {caps, _}, + {port, [{owner, TPid} | _]} + | _]]) -> TPid; -tpid(listen, Ref, [[{ref, Ref}, - {type, listen}, - {options, _}, - {accept, As} - | _]]) -> +tpid(Ref, [[{ref, Ref}, + {type, listen}, + {options, _}, + {accept, As} + | _]]) -> [[{watchdog, _}, {peer, _}, {apps, _}, @@ -375,6 +380,154 @@ tpid(listen, Ref, [[{ref, Ref}, TPid. %% =========================================================================== +%% # suspect/1 +%% =========================================================================== + +%% Configure transports to require a set number of watchdog timeouts +%% before moving from OKAY to SUSPECT. + +suspect(_) -> + [] = run([[abuse, [suspect, N]] || N <- [0,1,3]]). + +suspect(Type, Fake, Ref, N) + when is_reference(Ref) -> + {SvcName, TRef} + = start(Type, Ref, {?WD(10000), [{suspect, N}], mod(Fake)}), + {initial, okay} = ?WD_EVENT(TRef), + suspect(TRef, Fake, SvcName, N); + +suspect(TRef, true, SvcName, _) -> + reg(TRef, SvcName, 0), %% disable outgoing DWA + {okay, _} = ?WD_EVENT(TRef); + +suspect(TRef, false, SvcName, 0) -> %% SUSPECT disabled + %% Wait 2+ watchdogs and see that only one watchdog has been sent. + false = ?WD_EVENT(TRef, 28000), + [1,0,0,0] = wd_counts(SvcName); + +suspect(TRef, false, SvcName, N) -> + %% Check that no watchdog transition takes place within N+ + %% watchdogs ... + false = ?WD_EVENT(TRef, N*10000+8000), + [1,0,0,0] = wd_counts(SvcName), + %% ... but that the connection then becomes suspect ... + {okay, suspect} = ?WD_EVENT(TRef, 10000), + [1,0,0,0] = wd_counts(SvcName), + %% ... and goes down. + {suspect, down} = ?WD_EVENT(TRef, 18000), + [1,0,0,0] = wd_counts(SvcName). + +%% abuse/1 + +abuse(F) -> + [] = run([[abuse, F, T] || T <- [listen, connect]]). + +abuse(F, [_,_,_|_] = Args) -> + ?LOG("~p", [Args]), + apply(?MODULE, F, Args); + +abuse([F|A], Test) -> + Ref = make_ref(), + [] = run([[abuse, F, [T, T == Test, Ref] ++ A] + || T <- [listen, connect]]); + +abuse(F, Test) -> + abuse([F], Test). + +mod(true) -> + [{module, ?MODULE}]; +mod(false) -> + []. + +%% =========================================================================== +%% # okay/1 +%% =========================================================================== + +%% Configure the number of watchdog exchanges before moving from +%% REOPEN to OKAY. + +okay(_) -> + [] = run([[abuse, [okay, N]] || N <- [0,2,3]]). + +okay(Type, Fake, Ref, N) + when is_reference(Ref) -> + {SvcName, TRef} + = start(Type, Ref, {?WD(10000), + [{okay, choose(Fake, 0, N)}], + mod(Fake)}), + {initial, okay} = ?WD_EVENT(TRef), + okay(TRef, + Fake, + SvcName, + choose(Type == listen, initial, down), + N). + +okay(TRef, true, SvcName, Down, _) -> + reg(TRef, SvcName, 0), %% disable outgoing DWA + {okay, down} = ?WD_EVENT(TRef), + {Down, okay} = ?WD_EVENT(TRef), + reg(TRef, SvcName, -1), %% enable outgoing DWA + {okay, down} = ?WD_EVENT(TRef); + +okay(TRef, false, SvcName, Down, N) -> + {okay, suspect} = ?WD_EVENT(TRef), + [1,0,0,0] = wd_counts(SvcName), + {suspect, down} = ?WD_EVENT(TRef), + ok(TRef, SvcName, Down, N). + +ok(TRef, SvcName, Down, 0) -> + %% Connection comes up without watchdog exchange. + {Down, okay} = ?WD_EVENT(TRef), + [1,0,0,0] = wd_counts(SvcName), + %% Wait 2+ watchdog timeouts to see that the connection stays up + %% and two watchdogs are exchanged. + false = ?WD_EVENT(TRef, 28000), + [3,0,0,2] = wd_counts(SvcName); + +ok(TRef, SvcName, Down, N) -> + %% Connection required watchdog exchange before reaching OKAY. + {Down, reopen} = ?WD_EVENT(TRef), + {reopen, okay} = ?WD_EVENT(TRef), + %% One DWR was sent in moving to expect, plus N more to reopen the + %% connection. + N1 = N+1, + [N1,0,0,N] = wd_counts(SvcName). + +%% =========================================================================== + +%% wd_counts/1 + +wd_counts(SvcName) -> + [Info] = diameter:service_info(SvcName, transport), + {_, Counters} = lists:keyfind(statistics, 1, Info), + [proplists:get_value({{0,280,R}, D}, Counters, 0) || D <- [send,recv], + R <- [1,0]]. + +%% start/3 + +start(Type, Ref, T) -> + Name = hostname(), + true = diameter:subscribe(Name), + ok = diameter:start_service(Name, [{monitor, self()} | ?SERVICE(Name)]), + {ok, TRef} = diameter:add_transport(Name, {Type, opts(Type, Ref, T)}), + true = diameter_reg:add_new({Type, Ref, Name}), + {Name, TRef}. + +opts(Type, Ref, {Timer, Config, Mod}) -> + [{transport_module, diameter_tcp}, + {transport_config, Mod ++ [{ip, ?ADDR}, {port, 0}] ++ cfg(Type, Ref)}, + {watchdog_timer, Timer}, + {watchdog_config, Config}]. + +cfg(listen, _) -> + []; +cfg(connect, Ref) -> + [{{_, _, SvcName}, _Pid}] = diameter_reg:wait({listen, Ref, '_'}), + [[{ref, LRef} | _]] = diameter:service_info(SvcName, transport), + [LP] = ?util:lport(tcp, LRef), + [{raddr, ?ADDR}, {rport, LP}]. + +%% =========================================================================== listen(PortNr, Opts) -> gen_tcp:listen(PortNr, Opts). @@ -396,6 +549,7 @@ send(Sock, Bin) -> %% First outgoing message from a new transport process is CER/CEA. %% Remaining outgoing messages are either DWR or DWA. send(undefined, Sock, Bin) -> + <<_:32, _:8, 257:24, _/binary>> = Bin, putr(config, init), gen_tcp:send(Sock, Bin); @@ -505,15 +659,10 @@ run1([F|A]) -> catch E:R -> S = erlang:get_stacktrace(), - io:format("~p~n", [{A, E, R, S}]), + ?WARN("~p", [{A, E, R, S}]), S end. -%% now_diff/2 - -now_diff(T1, T2) -> - timer:now_diff(T2, T1). - %% jitter/2 jitter(?WD(T), _) -> diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index c4a713fb10..4fea62461c 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -29,12 +29,16 @@ MODULES = \ diameter_capx_SUITE \ diameter_codec_SUITE \ diameter_codec_test \ + diameter_config_SUITE \ diameter_compiler_SUITE \ diameter_dict_SUITE \ + diameter_distribution_SUITE \ diameter_dpr_SUITE \ diameter_event_SUITE \ + diameter_examples_SUITE \ diameter_failover_SUITE \ diameter_gen_sctp_SUITE \ + diameter_gen_tcp_SUITE \ diameter_length_SUITE \ diameter_reg_SUITE \ diameter_relay_SUITE \ diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 98e719c50a..757f29a32e 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,5 +18,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.4.1 +DIAMETER_VSN = 1.4.1.1 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) |