diff options
Diffstat (limited to 'lib/diameter')
63 files changed, 14868 insertions, 3335 deletions
diff --git a/lib/diameter/bin/diameterc b/lib/diameter/bin/diameterc index a72ba2d75c..2f5834d359 100755 --- a/lib/diameter/bin/diameterc +++ b/lib/diameter/bin/diameterc @@ -4,7 +4,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 @@ -124,10 +124,10 @@ arg(["-i", Dir | Args], #argv{options = Opts} = A) -> arg(["--name", Name | Args], #argv{options = Opts} = A) -> arg(Args, A#argv{options = [{name, Name} | Opts]}); - + arg(["--prefix", Name | Args], #argv{options = Opts} = A) -> arg(Args, A#argv{options = [{prefix, Name} | Opts]}); - + arg(["--inherits", Dict | Args], #argv{options = Opts} = A) -> arg(Args, A#argv{options = Opts ++ [{inherits, Dict}]}); @@ -166,7 +166,7 @@ path_exists(File, Type) -> _ -> throw({"No such file: ~p", [File]}) end. - + file_exists(File) -> path_exists(File, regular). diff --git a/lib/diameter/doc/src/Makefile b/lib/diameter/doc/src/Makefile index 0cbe1f000f..99a6680f12 100644 --- a/lib/diameter/doc/src/Makefile +++ b/lib/diameter/doc/src/Makefile @@ -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 @@ -35,45 +35,17 @@ XML_REF_FILES = $(XML_REF1_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES) XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) \ $(XML_REF_FILES) \ $(XML_PART_FILES) $(XML_CHAPTER_FILES) \ - seealso.ent - -INTERNAL_HTML_FILES = $(TECHNICAL_DESCR_FILES:%.xml=$(HTMLDIR)/%.html) - -HTML_APP_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) -HTML_EXTRA_FILES = $(XML_EXTRA_FILES:%.xml=$(HTMLDIR)/%.html) -HTML_PART_FILES = $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) - -HTML_FILES = $(HTML_APP_FILES) $(HTML_EXTRA_FILES) $(HTML_PART_FILES) + $(XML_EXTRA_FILES) INFO_FILE = ../../info -HTML_REF_FILES = $(XML_REF_FILES:%.xml=$(HTMLDIR)/%.html) - -HTML_CHAPTER_FILES = $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) - -EXTRA_FILES = \ - $(DEFAULT_GIF_FILES) \ - $(DEFAULT_HTML_FILES) \ - $(HTML_REF_FILES) \ - $(HTML_CHAPTER_FILES) - MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1) MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4) -HTML_REF_MAN_FILE = $(HTMLDIR)/index.html - -TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf STANDARD_DIR = ../standard -STANDARDS = - -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- - -XML_FLAGS += -DVIPS_FLAGS += # ---------------------------------------------------- # Targets @@ -86,11 +58,11 @@ docs: pdf html man ldocs: local_docs $(INDEX_TARGET) -$(TOP_PDF_FILE): $(XML_FILES) +$(PDF_FILE): $(XML_FILES) -pdf: $(TOP_PDF_FILE) +pdf: $(PDF_FILE) -html: gifs $(HTML_REF_MAN_FILE) +html: gifs $(HTMLDIR)/index.html clean clean_docs: clean_pdf clean_html clean_man rm -f errs core *~ @@ -120,7 +92,6 @@ depend: depend.mk debug opt: info: - @echo "->Makefile<-" @echo "" @echo "INDEX_FILE = $(INDEX_FILE)" @echo "INDEX_SRC = $(INDEX_SRC)" @@ -139,13 +110,9 @@ info: @echo "MAN3_FILES = $(MAN3_FILES)" @echo "MAN4_FILES = $(MAN4_FILES)" @echo "" - @echo "HTML_FILES = $(HTML_FILES)" - @echo "TOP_HTML_FILES = $(TOP_HTML_FILES)" - @echo "" @echo "DEFAULT_HTML_FILES = $(DEFAULT_HTML_FILES)" @echo "DEFAULT_GIF_FILES = $(DEFAULT_GIF_FILES)" @echo "" - @echo "" # ---------------------------------------------------- @@ -160,7 +127,7 @@ release_docs_spec: $(LOCAL)docs $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4" - $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" + $(INSTALL_DATA) $(PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(HTMLDIR)/*.* "$(RELSYSDIR)/doc/html" $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DATA) $(MAN1_FILES) "$(RELEASE_PATH)/man/man1" @@ -173,8 +140,8 @@ release_spec: depend.mk: depend.sed Makefile seealso.ent \ $(XML_REF_FILES) $(XML_CHAPTER_FILES) - $(gen_verbose)sed -f seehere.sed seealso.ent > seehere.ent - $(V_at)(for f in $(XML_REF_FILES) $(XML_CHAPTER_FILES); do \ + sed -f seehere.sed seealso.ent > seehere.ent + (for f in $(XML_REF_FILES) $(XML_CHAPTER_FILES); do \ sed -f $< $$f | sed "s@%FILE%@`basename $$f .xml`@g"; \ done) \ > $@ diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 7e50f338d3..eadc42536f 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 @@ -16,7 +18,7 @@ <header> <copyright> -<year>2011</year><year>2012</year> +<year>2011</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -41,7 +43,7 @@ under the License. <approved></approved> <checked></checked> <date></date> -<rev>%VSN%</rev> +<rev></rev> <file>diameter.xml</file> </header> @@ -188,7 +190,7 @@ Defaults to the value of the <c>alias</c> option if unspecified.</p> <item> <p> Specifies whether or not the &app_pick_peer; -application callback can modify the application state, +application callback can modify the application state. Defaults to <c>false</c> if unspecified.</p> <note> @@ -206,11 +208,13 @@ probably avoid it.</p> <item> <p> Determines the manner in which incoming answer messages containing -decode errors are handled. +decode errors are handled.</p> + +<p> If <c>callback</c> then errors result in a &app_handle_answer; callback in the same fashion as for &app_handle_request;, with errors communicated in the <c>errors</c> field of the -<c>#diameter_packet{}</c> record passed to the callback. +<c>#diameter_packet{}</c> passed to the callback. If <c>report</c> then an answer containing errors is discarded without a callback and a warning report is written to the log. If <c>discard</c> then an answer containing errors is silently @@ -224,6 +228,39 @@ question is as if a callback had taken place and returned Defaults to <c>report</c> if unspecified.</p> </item> +<tag><c>{request_errors, answer_3xxx|answer|callback}</c></tag> +<item> +<p> +Determines the manner in which incoming requests are handled when an +error other than 3007, DIAMETER_APPLICATION_UNSUPPORTED (which cannot +be associated with an application callback module), is detected.</p> + +<p> +If <c>answer_3xxx</c> then requests are answered without a +&app_handle_request; callback taking place. +If <c>answer</c> then even 5xxx errors are answered without a +callback unless the connection in question has configured the RFC 3588 +common dictionary as noted below. +If <c>callback</c> then a &app_handle_request; callback always takes +place and the return value determines the answer sent to the peer.</p> + +<p> +Defaults to <c>answer_3xxx</c> if unspecified.</p> + +<note> +<p> +Answers sent by diameter set the E-bit in the Diameter Header. +Since RFC 3588 allowed only 3xxx result codes in an +<c>answer-message</c>, <c>answer</c> has the same semantics as +<c>answer_3xxx</c> if the peer connection in question has configured +the RFC 3588 common dictionary, <c>diameter_gen_base_rfc3588</c>. +RFC 6733 allows both 3xxx and 5xxx result codes in an +<c>answer-message</c> so a connection configured with the RFC 6733 +common dictionary, <c>diameter_gen_base_rfc6733</c>, does +distinguish between <c>answer_3xxx</c> and <c>answer</c>.</p> +</note> +</item> + </taglist> <marker id="call_opt"/> @@ -534,7 +571,7 @@ Pkt = #diameter_packet{} The RFC 3539 watchdog state machine has transitioned into (<c>up</c>) or out of (<c>down</c>) the OKAY state. -If a <c>#diameter_packet{}</c> record is present in an <c>up</c> event +If a <c>#diameter_packet{}</c> is present in an <c>up</c> event then there has been a capabilties exchange on a newly established transport connection and the record contains the received CER or CEA. Otherwise a connection has reestablished without the loss or @@ -737,8 +774,8 @@ 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 @@ -784,6 +821,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> @@ -975,6 +1094,42 @@ configured them.</p> Defaults to a single callback returning <c>dpr</c>.</p> </item> +<marker id="length_errors"/> +<tag><c>{length_errors, exit|handle|discard}</c></tag> +<item> +<p> +Specifies how to deal with errors in the Message Length field of the +Diameter Header in an incoming message. +An error in this context is that the length is not at least 20 bytes +(the length of a Header), is not a multiple of 4 (a valid length) or +is not the length of the message in question, as received over the +transport interface documented in &man_transport;.</p> + +<p> +If <c>exit</c> then a warning report is emitted and the parent of the +transport process in question exits, which causes the transport +process itself to exit as described in &man_transport;. +If <c>handle</c> then the message is processed as usual, a resulting +&app_handle_request; or &app_handle_answer; callback (if one takes +place) indicating the <c>5015</c> error (DIAMETER_INVALID_MESSAGE_LENGTH). +If <c>discard</c> then the message in question is silently discarded.</p> + +<p> +Defaults to <c>exit</c>.</p> + +<note> +<p> +The default value reflects the fact that a transport module for a +stream-oriented transport like TCP may not be able to recover from a +message length error since such a transport must use the Message +Length header to divide the incoming byte stream into individual +Diameter messages. +An invalid length leaves it with no reliable way to rediscover message +boundaries, which may result in the failure of subsequent messages. +See &man_tcp; for the behaviour of that module.</p> +</note> +</item> + <marker id="reconnect_timer"/> <tag><c>{reconnect_timer, Tc}</c></tag> <item> @@ -1054,6 +1209,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 f4db625c71..d4fb792787 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -13,7 +13,7 @@ <header> <copyright> -<year>2011</year><year>2012</year> +<year>2011</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -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> @@ -475,6 +483,7 @@ not selected.</p> | discard | {eval|eval_packet, Action, PostF}</v> <v>Reply = {reply, &packet; | &message;} + | {answer_message, 3000..3999|5000..5999} | {protocol_error, 3000..3999}</v> <v>Opt = &mod_call_opt;</v> <v>PostF = &mod_evaluable;</v> @@ -509,14 +518,15 @@ Otherwise it contains the record representing the request as outlined in &dict;.</p> <p> -The <c>errors</c> field specifies any Result-Code's identifying errors -that were encountered in decoding the request. -In this case diameter will set both Result-Code and -Failed-AVP AVP's in a returned -answer &message; before sending it to the peer: -the returned &message; need only set any other required AVP's. -Note that the errors detected by diameter are all of the 5xxx series -(Permanent Failures). +The <c>errors</c> field specifies any results codes identifying errors +found while decoding the request. +This is used to set Result-Code and/or Failed-AVP in a returned +answer unless the callback returns a <c>#diameter_packet{}</c> +whose <c>errors</c> field is set to either a non-empty list of its +own, in which case this list is used instead, or the atom <c>false</c> +to disable any setting of Result-Code and Failed-AVP. +Note that the errors detected by diameter are of the 3xxx +and 5xxx series, Protocol Errors and Permanent Failures respectively. The <c>errors</c> list is empty if the request has been received in the relay application.</p> @@ -544,24 +554,25 @@ preserved in the outgoing answer, appropriate values otherwise being set by diameter.</p> </item> -<tag><c>{protocol_error, 3000..3999}</c></tag> +<tag><c>{answer_message, 3000..3999|5000..5999}</c></tag> <item> <p> Send an answer message to the peer containing the specified -protocol error. +Result-Code. Equivalent to</p> <pre> {reply, ['answer-message' | Avps] </pre> <p> where <c>Avps</c> sets the Origin-Host, Origin-Realm, the specified -Result-Code and (if the request sent one) Session-Id AVP's.</p> +Result-Code and (if the request contained one) Session-Id AVP's.</p> <p> -Note that &the_rfc; mandates that only answers with a 3xxx series -Result-Code (protocol errors) may set the E bit. -Returning a non-3xxx value in a <c>protocol_error</c> tuple -will cause the request process in question to fail.</p> +Returning a value other than 3xxx or 5xxx will cause the request +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> </item> <tag><c>{relay, Opts}</c></tag> @@ -614,11 +625,20 @@ containing the encoded binary. The return value is ignored.</p> </item> +<tag><c>{protocol_error, 3000..3999}</c></tag> +<item> +<p> +Equivalent to <c>{answer_message, 3000..3999}</c>.</p> +</item> + </taglist> +<note> <p> -Note that protocol errors detected by diameter will result in an -answer message without <c>handle_request/3</c> being invoked.</p> +Requests containing errors may be answered by diameter, without a +callback taking place, depending on the value of the +&mod_application_opt; <c>request_errors</c>.</p> +</note> </desc> </func> diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 8b0687a22e..1034781ff2 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -16,7 +16,7 @@ <header> <copyright> -<year>2011</year><year>2012</year> +<year>2011</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -76,14 +76,18 @@ The generated hrl also contains macro definitions for the possible values of AVPs of type Enumerated.</p> <p> -The diameter application includes three dictionary modules +The diameter application includes five dictionary modules corresponding to applications defined in section 2.4 of &the_rfc;: -<c>diameter_gen_base_rfc3588</c> for the Diameter Common Messages -application with application identifier 0, -<c>diameter_gen_accounting</c> for the Diameter Base Accounting +<c>diameter_gen_base_rfc3588</c> and <c>diameter_gen_base_rfc6733</c> +for the Diameter Common Messages application with application +identifier 0, +<c>diameter_gen_accounting</c> (for RFC 3588) and +<c>diameter_gen_acct_rfc6733</c> for the Diameter Base Accounting application with application identifier 3 and <c>diameter_gen_relay</c> the Relay application with application -identifier 0xFFFFFFFF. +identifier 0xFFFFFFFF.</p> + +<p> The Common Message and Relay applications are the only applications that diameter itself has any specific knowledge of. The Common Message application is used for messages that diameter diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml index 5e3fd5eaf1..df140b16b9 100644 --- a/lib/diameter/doc/src/diameter_sctp.xml +++ b/lib/diameter/doc/src/diameter_sctp.xml @@ -15,7 +15,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> @@ -81,7 +81,6 @@ and implements the behaviour documented in The start function required by &man_transport;.</p> <p> -The only diameter_sctp-specific argument is the options list. Options <c>raddr</c> and <c>rport</c> specify the remote address and port for a connecting transport and not valid for a listening transport: the former is required while latter defaults to 3868 if diff --git a/lib/diameter/doc/src/diameter_soc.xml b/lib/diameter/doc/src/diameter_soc.xml index 16f6b9d5bb..4f5419122f 100644 --- a/lib/diameter/doc/src/diameter_soc.xml +++ b/lib/diameter/doc/src/diameter_soc.xml @@ -4,12 +4,12 @@ %also; ]> -<chapter> +<chapter xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> <year>2011</year> -<year>2012</year> +<year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> @@ -80,6 +80,8 @@ just be unnecessary configuration of a value that it has no control over.</p> </list> +<xi:include href="diameter_soc_rfc6733.xml"/> + </section> <!-- ===================================================================== --> diff --git a/lib/diameter/doc/src/diameter_soc_rfc6733.xml b/lib/diameter/doc/src/diameter_soc_rfc6733.xml new file mode 100644 index 0000000000..8d85569650 --- /dev/null +++ b/lib/diameter/doc/src/diameter_soc_rfc6733.xml @@ -0,0 +1,8692 @@ +<?xml version="1.0" encoding="latin1" ?> + +<!-- + +<copyright> +<year>2013</year> +<holder>Ericsson AB. All Rights Reserved.</holder> +</copyright> + +<legalnotice> +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. + +</legalnotice> + +--> + +<!DOCTYPE section SYSTEM "chapter.dtd" [ + <!ENTITY gen_sctp '<seealso marker="kernel:gen_sctp">gen_sctp(3)</seealso>'> + <!ENTITY gen_tcp '<seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>'> + <!ENTITY service '<seealso marker="diameter#start_service-2">service</seealso>'> + <!ENTITY capabilities '<seealso marker="diameter#capability">capabilities</seealso>'> + <!ENTITY events '<seealso marker="diameter#service_event">events</seealso>'> + <!ENTITY nada '<p>No comment.</p>'> + <!ENTITY % also SYSTEM "seealso.ent" > + %also; +]> + +<section> +<title>Commentary</title> + +<p> +A more detailed commentary on &the_rfc; follows. +Its purpose is to (hopefully) clarify not only what is supported but +how, given that semantics and features discussed in the RFC are not +solely the responsibility of the diameter application: +in many cases much depends on the configuration a user passes to +diameter, the implementation of &man_app; callback modules in +particular.</p> + +<p> +Comments apply to all text following the preceding comment. +Be sure to distinguish between capitalized <em>Diameter</em>, the +protocol defined by the RFC, and lowercase <em>diameter</em>, the +Erlang application to which the commentary applies.</p> + +<warning> +<p> +The commentary is not yet complete. +Comments currently stop at chapter 4.</p> +</warning> + +<pre> +Fajardo, et al. Standards Track [Page 6] + +RFC 6733 Diameter Base Protocol October 2012 + + +1. Introduction + + Authentication, Authorization, and Accounting (AAA) protocols such as + TACACS [RFC1492] and RADIUS [RFC2865] were initially deployed to + provide dial-up PPP [RFC1661] and terminal server access. Over time, + AAA support was needed on many new access technologies, the scale and + complexity of AAA networks grew, and AAA was also used on new + applications (such as voice over IP). This led to new demands on AAA + protocols. +</pre> + +<p> +Note that diameter implements the Diameter protocol as defined in +&the_rfc;. +It also supported the previous version of the protocol, as defined in +RFC 3588, when there are differences. +(Which will be noted below.) +It does not support RADIUS.</p> + +<pre> + + Network access requirements for AAA protocols are summarized in + Aboba, et al. [RFC2989]. These include: + + Failover + + [RFC2865] does not define failover mechanisms and, as a result, + failover behavior differs between implementations. In order to + provide well-defined failover behavior, Diameter supports + application-layer acknowledgements and defines failover algorithms + and the associated state machine. +</pre> + +&nada; + +<pre> + + Transmission-level security + + RADIUS [RFC2865] defines an application-layer authentication and + integrity scheme that is required only for use with response + packets. While [RFC2869] defines an additional authentication and + integrity mechanism, use is only required during Extensible + Authentication Protocol (EAP) [RFC3748] sessions. While attribute + hiding is supported, [RFC2865] does not provide support for per- + packet confidentiality. In accounting, [RFC2866] assumes that + replay protection is provided by the backend billing server rather + than within the protocol itself. + + While [RFC3162] defines the use of IPsec with RADIUS, support for + IPsec is not required. In order to provide universal support for + transmission-level security, and enable both intra- and inter- + domain AAA deployments, Diameter provides support for TLS/TCP and + DTLS/SCTP. Security is discussed in Section 13. +</pre> + +<p> +Whether or not IPsec is used is transparent to diameter.</p> + +<p> +The transport protocol used on a given peer connection is also +transparent to diameter in that transport to diameter is simply a +module that implements the transport protocol documented in +&man_transport;. +A diameter user configures this module as the &mod_transport_opt; +<c>transport_module</c>.</p> + +<p> +While a user can implement their own transport modules, diameter +includes implementations for TCP and SCTP: +&man_tcp; based on &gen_tcp; and &man_sctp; based on &gen_sctp;. +The former supports TLS but the latter does not currently support +DTLS.</p> + +<pre> + + Reliable transport + + RADIUS runs over UDP, and does not define retransmission behavior; + as a result, reliability varies between implementations. As + described in [RFC2975], this is a major issue in accounting, where + packet loss may translate directly into revenue loss. In order to + + + + + + +Fajardo, et al. Standards Track [Page 7] + +RFC 6733 Diameter Base Protocol October 2012 + + + provide well-defined transport behavior, Diameter runs over + reliable transport mechanisms (TCP, Stream Control Transmission + Protocol (SCTP)) as defined in [RFC3539]. + + Agent support + + RADIUS does not provide for explicit support for agents, including + proxies, redirects, and relays. Since the expected behavior is + not defined, it varies between implementations. Diameter defines + agent behavior explicitly; this is described in Section 2.8. +</pre> + +&nada; + +<pre> + + Server-initiated messages + + While server-initiated messages are defined in RADIUS [RFC5176], + support is optional. This makes it difficult to implement + features such as unsolicited disconnect or re-authentication/ + re-authorization on demand across a heterogeneous deployment. To + address this issue, support for server-initiated messages is + mandatory in Diameter. +</pre> + +<p> +A diameter user can both send and receive messages.</p> + +<pre> + + Transition support + + While Diameter does not share a common protocol data unit (PDU) + with RADIUS, considerable effort has been expended in enabling + backward compatibility with RADIUS so that the two protocols may + be deployed in the same network. Initially, it is expected that + Diameter will be deployed within new network devices, as well as + within gateways enabling communication between legacy RADIUS + devices and Diameter agents. This capability enables Diameter + support to be added to legacy networks, by addition of a gateway + or server speaking both RADIUS and Diameter. +</pre> + +<p> +RADIUS Attributes can be redefined as Diameter AVP's using diameter's +&man_dict; interface but diameter provides no such definitions.</p> + +<pre> + + In addition to addressing the above requirements, Diameter also + provides support for the following: + + Capability negotiation + + RADIUS does not support error messages, capability negotiation, or + a mandatory/non-mandatory flag for attributes. Since RADIUS + clients and servers are not aware of each other's capabilities, + they may not be able to successfully negotiate a mutually + acceptable service or, in some cases, even be aware of what + service has been implemented. Diameter includes support for error + handling (Section 7), capability negotiation (Section 5.3), and + mandatory/non-mandatory Attribute-Value Pairs (AVPs) + (Section 4.1). + + + + + +Fajardo, et al. Standards Track [Page 8] + +RFC 6733 Diameter Base Protocol October 2012 + + + Peer discovery and configuration + + RADIUS implementations typically require that the name or address + of servers or clients be manually configured, along with the + corresponding shared secrets. This results in a large + administrative burden and creates the temptation to reuse the + RADIUS shared secret, which can result in major security + vulnerabilities if the Request Authenticator is not globally and + temporally unique as required in [RFC2865]. Through DNS, Diameter + enables dynamic discovery of peers (see Section 5.2). Derivation + of dynamic session keys is enabled via transmission-level + security. + + Over time, the capabilities of Network Access Server (NAS) devices + have increased substantially. As a result, while Diameter is a + considerably more sophisticated protocol than RADIUS, it remains + feasible to implement it within embedded devices. +</pre> + +&nada; + +<pre> + +1.1. Diameter Protocol + + The Diameter base protocol provides the following facilities: + + o Ability to exchange messages and deliver AVPs +</pre> + +<p> +There are two interfaces directly involved in message exchange when +using diameter: the function &mod_call; for sending outgoing requests, +and the application callback interface, documented in &man_app; for +receiving incoming request and answers.</p> + +<pre> + + o Capabilities negotiation +</pre> + +<p> +Capabilities negotiation is the responsibility of diameter: +a user configures a diameter service and/or transport with +&capabilities; to provide AVP values for CER and CEA messages but it +is diameter itself that sends these messages. +A user receives notification of a successful capabilities exchange by +way of &app_peer_up; callbacks.</p> + +<pre> + + o Error notification +</pre> + +<p> +A user can subscribe to &events;, using &mod_subscribe;, in order to +receive notification of various failures. +Errors in Diameter messaging are communicated via the application +callbacks &app_handle_request;, &app_handle_answer; and +&app_handle_error;.</p> + + +<pre> + + o Extensibility, required in [RFC2989], through addition of new + applications, commands, and AVPs +</pre> + +<p> +Support for applications, commands and AVP's is extensible using +diameter's dictionary interface, as documented in &man_dict;. +Dictionaries are compiled to Erlang encode/decode modules using +&man_compile; or &man_make;.</p> + +<pre> + + o Basic services necessary for applications, such as the handling of + user sessions or accounting +</pre> + +<p> +Compiled dictionaries are provided for the RFC 3588 and RFC 6733 +Diameter applications: common, base accounting and relay. +Dictionaries for a number of standardized +applications are provided in uncompiled form below the <c>examples</c> +subdirectory of the diameter application directory.</p> + +<pre> + + All data delivered by the protocol is in the form of AVPs. Some of + these AVP values are used by the Diameter protocol itself, while + others deliver data associated with particular applications that + employ Diameter. AVPs may be arbitrarily added to Diameter messages, + the only restriction being that the Command Code Format (CCF) + specification (Section 3.2) be satisfied. AVPs are used by the base + Diameter protocol to support the following required features: + + o Transporting of user authentication information, for the purposes + of enabling the Diameter server to authenticate the user + + o Transporting of service-specific authorization information, + between client and servers, allowing the peers to decide whether a + user's access request should be granted + + + +Fajardo, et al. Standards Track [Page 9] + +RFC 6733 Diameter Base Protocol October 2012 + + + o Exchanging resource usage information, which may be used for + accounting purposes, capacity planning, etc. + + o Routing, relaying, proxying, and redirecting of Diameter messages + through a server hierarchy + + The Diameter base protocol satisfies the minimum requirements for a + AAA protocol, as specified by [RFC2989]. The base protocol may be + used by itself for accounting purposes only, or it may be used with a + Diameter application, such as Mobile IPv4 [RFC4004], or network + access [RFC4005]. It is also possible for the base protocol to be + extended for use in new applications, via the addition of new + commands or AVPs. The initial focus of Diameter was network access + and accounting applications. A truly generic AAA protocol used by + many applications might provide functionality not provided by + Diameter. Therefore, it is imperative that the designers of new + applications understand their requirements before using Diameter. + See Section 1.3.4 for more information on Diameter applications. + + Any node can initiate a request. In that sense, Diameter is a peer- + to-peer protocol. In this document, a Diameter client is a device at + the edge of the network that performs access control, such as a + Network Access Server (NAS) or a Foreign Agent (FA). A Diameter + client generates Diameter messages to request authentication, + authorization, and accounting services for the user. A Diameter + agent is a node that does not provide local user authentication or + authorization services; agents include proxies, redirects, and relay + agents. A Diameter server performs authentication and/or + authorization of the user. A Diameter node may act as an agent for + certain requests while acting as a server for others. + + The Diameter protocol also supports server-initiated messages, such + as a request to abort service to a particular user. +</pre> + +&nada; + +<pre> + +1.1.1. Description of the Document Set + + The Diameter specification consists of an updated version of the base + protocol specification (this document) and the Transport Profile + [RFC3539]. This document obsoletes both RFC 3588 and RFC 5719. A + summary of the base protocol updates included in this document can be + found in Section 1.1.3. + + This document defines the base protocol specification for AAA, which + includes support for accounting. There are also a myriad of + applications documents describing applications that use this base + specification for Authentication, Authorization, and Accounting. + These application documents specify how to use the Diameter protocol + within the context of their application. + + + +Fajardo, et al. Standards Track [Page 10] + +RFC 6733 Diameter Base Protocol October 2012 + + + The Transport Profile document [RFC3539] discusses transport layer + issues that arise with AAA protocols and recommendations on how to + overcome these issues. This document also defines the Diameter + failover algorithm and state machine. + + "Clarifications on the Routing of Diameter Request Based on the + Username and the Realm" [RFC5729] defines specific behavior on how to + route requests based on the content of the User-Name AVP (Attribute + Value Pair). + +1.1.2. Conventions Used in This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. +</pre> + +&nada; + +<pre> + +1.1.3. Changes from RFC 3588 + + This document obsoletes RFC 3588 but is fully backward compatible + with that document. The changes introduced in this document focus on + fixing issues that have surfaced during the implementation of + Diameter (RFC 3588). An overview of some the major changes are given + below. +</pre> + +<p> +RFC 6733 is not fully backwards compatible with RFC 3588. +(For example, in what values of Result-Code values are permissible with +the E-bit.) +The implications of incompatibilities for diameter are noted where +appropriate.</p> + +<pre> + + o Deprecated the use of the Inband-Security AVP for negotiating + Transport Layer Security (TLS) [RFC5246]. It has been generally + considered that bootstrapping of TLS via Inband-Security AVP + creates certain security risks because it does not completely + protect the information carried in the CER/CEA (Capabilities- + Exchange-Request/Capabilities-Exchange-Answer). This version of + Diameter adopts the common approach of defining a well-known + secured port that peers should use when communicating via TLS/TCP + and DTLS/SCTP. This new approach augments the existing in-band + security negotiation, but it does not completely replace it. The + old method is kept for backward compatibility reasons. +</pre> + +<p> +&man_tcp; supports both methods of negotiating TLS: +bootstrapping via Inband-Security and directly following connection +establishment.</p> + +<pre> + + o Deprecated the exchange of CER/CEA messages in the open state. + This feature was implied in the peer state machine table of RFC + 3588, but it was not clearly defined anywhere else in that + document. As work on this document progressed, it became clear + that the multiplicity of meaning and use of Application-Id AVPs in + the CER/CEA messages (and the messages themselves) is seen as an + abuse of the Diameter extensibility rules and thus required + simplification. Capabilities exchange in the open state has been + re-introduced in a separate specification [RFC6737], which clearly + defines new commands for this feature. +</pre> + +<p> +Capabilities exchange in the open state is not supported: an incoming +CER in the open state will cause diameter to ask the relevant +transport process to terminate, which implies the loss of the peer +connection in the case of &man_tcp; and &man_sctp;.</p> + +<p> +Capabilities update, as defined by RFC 6737, is not yet supported. +Support will require diameter to handle CUR/CUA in the same way that +it handles CER/CEA.</p> + +<pre> + + + + + +Fajardo, et al. Standards Track [Page 11] + +RFC 6733 Diameter Base Protocol October 2012 + + + o Simplified security requirements. The use of a secured transport + for exchanging Diameter messages remains mandatory. However, TLS/ + TCP and DTLS/SCTP have become the primary methods of securing + Diameter with IPsec as a secondary alternative. See Section 13 + for details. The support for the End-to-End security framework + (E2E-Sequence AVP and 'P'-bit in the AVP header) has also been + deprecated. +</pre> + +<p> +The End-to-End security framework is not supported since it's use is +largely unspecified: diameter will set the P-bit in outgoing AVP's as +directed by the relevant dictionary and/or &app_prepare_request; or +&app_handle_request; callbacks, but whether or not the P-bit is set on +incoming AVP's has no consequence.</p> + +<p> +As noted above, DTLS is not currently supported and whether or not +IPsec is used is transparent to diameter.</p> + +<pre> + + o Changed Diameter extensibility. This includes fixes to the + Diameter extensibility description (Section 1.3 and others) to + better aid Diameter application designers; in addition, the new + specification relaxes the policy with respect to the allocation of + Command Codes for vendor-specific uses. + + o Clarified Application Id usage. Clarify the proper use of + Application Id information, which can be found in multiple places + within a Diameter message. This includes correlating Application + Ids found in the message headers and AVPs. These changes also + clearly specify the proper Application Id value to use for + specific base protocol messages (ASR/ASA, STR/STA) as well as + clarify the content and use of Vendor-Specific-Application-Id. + + o Clarified routing fixes. This document more clearly specifies + what information (AVPs and Application Ids) can be used for making + general routing decisions. A rule for the prioritization of + redirect routing criteria when multiple route entries are found + via redirects has also been added (see Section 6.13). + + o Simplified Diameter peer discovery. The Diameter discovery + process now supports only widely used discovery schemes; the rest + have been deprecated (see Section 5.2 for details). +</pre> + +<p> +Peer discover is not currently supported: peers to which a node should +connect must be configured. +Connection requests are accepted from arbitrary peers but a +&mod_transport_opt; <c>capabilities_cb</c> can be used to reject a +peer based on an incoming CER or CEA.</p> + +<pre> + + There are many other miscellaneous fixes that have been introduced in + this document that may not be considered significant, but they have + value nonetheless. Examples are removal of obsolete types, fixes to + the state machine, clarification of the election process, message + validation, fixes to Failed-AVP and Result-Code AVP values, etc. All + of the errata filed against RFC 3588 prior to the publication of this + document have been addressed. A comprehensive list of changes is not + shown here for practical reasons. + +1.2. Terminology + + AAA + + Authentication, Authorization, and Accounting. + + + + + +Fajardo, et al. Standards Track [Page 12] + +RFC 6733 Diameter Base Protocol October 2012 + + + ABNF + + Augmented Backus-Naur Form [RFC5234]. A metalanguage with its own + formal syntax and rules. It is based on the Backus-Naur Form and + is used to define message exchanges in a bi-directional + communications protocol. + + Accounting + + The act of collecting information on resource usage for the + purpose of capacity planning, auditing, billing, or cost + allocation. + + Accounting Record + + An accounting record represents a summary of the resource + consumption of a user over the entire session. Accounting servers + creating the accounting record may do so by processing interim + accounting events or accounting events from several devices + serving the same user. + + Authentication + + The act of verifying the identity of an entity (subject). + + Authorization + + The act of determining whether a requesting entity (subject) will + be allowed access to a resource (object). + + Attribute-Value Pair (AVP) + + The Diameter protocol consists of a header followed by one or more + Attribute-Value-Pairs (AVPs). An AVP includes a header and is + used to encapsulate protocol-specific data (e.g., routing + information) as well as authentication, authorization, or + accounting information. +</pre> + +&nada; + +<pre> + + Command Code Format (CCF) + + A modified form of ABNF used to define Diameter commands (see + Section 3.2). +</pre> + +<p> +The <c>@messages</c> section of the &man_dict; format has the CCF as +content.</p> + +<pre> + + Diameter Agent + + A Diameter Agent is a Diameter node that provides relay, proxy, + redirect, or translation services. + + + + +Fajardo, et al. Standards Track [Page 13] + +RFC 6733 Diameter Base Protocol October 2012 + + + Diameter Client + + A Diameter client is a Diameter node that supports Diameter client + applications as well as the base protocol. Diameter clients are + often implemented in devices situated at the edge of a network and + provide access control services for that network. Typical + examples of Diameter clients include the Network Access Server + (NAS) and the Mobile IP Foreign Agent (FA). + + Diameter Node + + A Diameter node is a host process that implements the Diameter + protocol and acts as either a client, an agent, or a server. + + Diameter Peer + + Two Diameter nodes sharing a direct TCP or SCTP transport + connection are called Diameter peers. + + Diameter Server + + A Diameter server is a Diameter node that handles authentication, + authorization, and accounting requests for a particular realm. By + its very nature, a Diameter server must support Diameter server + applications in addition to the base protocol. +</pre> + +<p> +A Diameter Node is implemented by configuring a service +using &mod_start_service; and one or more transports using +&mod_add_transport;. +The service typically represents a Diameter Node but since +capabilities can be configured on individual transports it's more +accurate to say that the node is a collection of transports +advertising the same Origin-Host.</p> + +<p> +The role of a node (agent, client or server) is not something that's +configured explicitly. +Transports are either connecting or listening, depending on whether +diameter should establish a peer connection and send CER or accept +connections and receive CER, but the role a node implements depends +largely on dictionary configuration and &man_app; callback +implementation.</p> + +<pre> + + Downstream + + Downstream is used to identify the direction of a particular + Diameter message from the home server towards the Diameter client. + + Home Realm + + A Home Realm is the administrative domain with which the user + maintains an account relationship. + + Home Server + + A Diameter server that serves the Home Realm. + + Interim Accounting + + An interim accounting message provides a snapshot of usage during + a user's session. Typically, it is implemented in order to + provide for partial accounting of a user's session in case a + device reboot or other network problem prevents the delivery of a + session summary message or session record. + + + + +Fajardo, et al. Standards Track [Page 14] + +RFC 6733 Diameter Base Protocol October 2012 + + + Local Realm + + A local realm is the administrative domain providing services to a + user. An administrative domain may act as a local realm for + certain users while being a home realm for others. + + Multi-session + + A multi-session represents a logical linking of several sessions. + Multi-sessions are tracked by using the Acct-Multi-Session-Id. An + example of a multi-session would be a Multi-link PPP bundle. Each + leg of the bundle would be a session while the entire bundle would + be a multi-session. + + Network Access Identifier + + The Network Access Identifier, or NAI [RFC4282], is used in the + Diameter protocol to extract a user's identity and realm. The + identity is used to identify the user during authentication and/or + authorization while the realm is used for message routing + purposes. + + Proxy Agent or Proxy + + In addition to forwarding requests and responses, proxies make + policy decisions relating to resource usage and provisioning. + Typically, this is accomplished by tracking the state of NAS + devices. While proxies usually do not respond to client requests + prior to receiving a response from the server, they may originate + Reject messages in cases where policies are violated. As a + result, proxies need to understand the semantics of the messages + passing through them, and they may not support all Diameter + applications. + + Realm + + The string in the NAI that immediately follows the '@' character. + NAI realm names are required to be unique and are piggybacked on + the administration of the DNS namespace. Diameter makes use of + the realm, also loosely referred to as domain, to determine + whether messages can be satisfied locally or whether they must be + routed or redirected. In RADIUS, realm names are not necessarily + piggybacked on the DNS namespace but may be independent of it. + + + + + + + + +Fajardo, et al. Standards Track [Page 15] + +RFC 6733 Diameter Base Protocol October 2012 + + + Real-Time Accounting + + Real-time accounting involves the processing of information on + resource usage within a defined time window. Typically, time + constraints are imposed in order to limit financial risk. The + Diameter Credit-Control Application [RFC4006] is an example of an + application that defines real-time accounting functionality. + + Relay Agent or Relay + + Relays forward requests and responses based on routing-related + AVPs and routing table entries. Since relays do not make policy + decisions, they do not examine or alter non-routing AVPs. As a + result, relays never originate messages, do not need to understand + the semantics of messages or non-routing AVPs, and are capable of + handling any Diameter application or message type. Since relays + make decisions based on information in routing AVPs and realm + forwarding tables, they do not keep state on NAS resource usage or + sessions in progress. + + Redirect Agent + + Rather than forwarding requests and responses between clients and + servers, redirect agents refer clients to servers and allow them + to communicate directly. Since redirect agents do not sit in the + forwarding path, they do not alter any AVPs transiting between + client and server. Redirect agents do not originate messages and + are capable of handling any message type, although they may be + configured only to redirect messages of certain types, while + acting as relay or proxy agents for other types. As with relay + agents, redirect agents do not keep state with respect to sessions + or NAS resources. +</pre> + +&nada; + +<pre> + + Session + + A session is a related progression of events devoted to a + particular activity. Diameter application documents provide + guidelines as to when a session begins and ends. All Diameter + packets with the same Session-Id are considered to be part of the + same session. +</pre> + +<p> +Sessions are not something that diameter is aware of. +The function &mod_session_id; can be used to construct appropriate +values for Session-Id AVP's but logic connecting events in the same +session is the responsibility of the diameter user.</p> + +<pre> + + Stateful Agent + + A stateful agent is one that maintains session state information, + by keeping track of all authorized active sessions. Each + authorized session is bound to a particular service, and its state + is considered active either until it is notified otherwise or + until expiration. + + + +Fajardo, et al. Standards Track [Page 16] + +RFC 6733 Diameter Base Protocol October 2012 + + + Sub-session + + A sub-session represents a distinct service (e.g., QoS or data + characteristics) provided to a given session. These services may + happen concurrently (e.g., simultaneous voice and data transfer + during the same session) or serially. These changes in sessions + are tracked with the Accounting-Sub-Session-Id. + + Transaction State + + The Diameter protocol requires that agents maintain transaction + state, which is used for failover purposes. Transaction state + implies that upon forwarding a request, the Hop-by-Hop Identifier + is saved; the field is replaced with a locally unique identifier, + which is restored to its original value when the corresponding + answer is received. The request's state is released upon receipt + of the answer. A stateless agent is one that only maintains + transaction state. + + Translation Agent + + A translation agent (TLA in Figure 4) is a stateful Diameter node + that performs protocol translation between Diameter and another + AAA protocol, such as RADIUS. + + Upstream + + Upstream is used to identify the direction of a particular + Diameter message from the Diameter client towards the home server. + + User + + The entity or device requesting or using some resource, in support + of which a Diameter client has generated a request. +</pre> + +&nada; + +<pre> + +1.3. Approach to Extensibility + + The Diameter protocol is designed to be extensible, using several + mechanisms, including: + + o Defining new AVP values + + o Creating new AVPs + + o Creating new commands + + o Creating new applications + + + + +Fajardo, et al. Standards Track [Page 17] + +RFC 6733 Diameter Base Protocol October 2012 + + + From the point of view of extensibility, Diameter authentication, + authorization, and accounting applications are treated in the same + way. +</pre> + +<p> +Extensibility in diameter is by way of the dictionary interface +documented in &man_dict;: a diameter user creates applications, +commands and AVP's by implementing a new dictionary, +compiling the dictionary to a codec module using &man_compile; or +&man_make;, and configuring the resulting dictionary module on a +service. +The dictionary modules provided with diameter are all implemented in +this manner.</p> + +<pre> + Note: Protocol designers should try to reuse existing functionality, + namely AVP values, AVPs, commands, and Diameter applications. Reuse + simplifies standardization and implementation. To avoid potential + interoperability issues, it is important to ensure that the semantics + of the reused features are well understood. Given that Diameter can + also carry RADIUS attributes as Diameter AVPs, such reuse + considerations also apply to existing RADIUS attributes that may be + useful in a Diameter application. +</pre> + +<p> +Reuse in dictionary files is achieved by way of the <c>@inherits</c> +section. +AVP's are inherited, commands are not.</p> + +<pre> + +1.3.1. Defining New AVP Values + + In order to allocate a new AVP value for AVPs defined in the Diameter + base protocol, the IETF needs to approve a new RFC that describes the + AVP value. IANA considerations for these AVP values are discussed in + Section 11.3. + + The allocation of AVP values for other AVPs is guided by the IANA + considerations of the document that defines those AVPs. Typically, + allocation of new values for an AVP defined in an RFC would require + IETF Review [RFC5226], whereas values for vendor-specific AVPs can be + allocated by the vendor. + +1.3.2. Creating New AVPs + + A new AVP being defined MUST use one of the data types listed in + Sections 4.2 or 4.3. If an appropriate derived data type is already + defined, it SHOULD be used instead of a base data type to encourage + reusability and good design practice. + + In the event that a logical grouping of AVPs is necessary, and + multiple "groups" are possible in a given command, it is recommended + that a Grouped AVP be used (see Section 4.4). + + The creation of new AVPs can happen in various ways. The recommended + approach is to define a new general-purpose AVP in a Standards Track + RFC approved by the IETF. However, as described in Section 11.1.1, + there are other mechanisms. +</pre> + +<p> +Creating new AVP's is an issue for the dictionary designer, not +diameter.</p> + +<pre> + +1.3.3. Creating New Commands + + A new Command Code MUST be allocated when required AVPs (those + indicated as {AVP} in the CCF definition) are added to, deleted from, + or redefined in (for example, by changing a required AVP into an + optional one) an existing command. + + + +Fajardo, et al. Standards Track [Page 18] + +RFC 6733 Diameter Base Protocol October 2012 + + + Furthermore, if the transport characteristics of a command are + changed (for example, with respect to the number of round trips + required), a new Command Code MUST be registered. + + A change to the CCF of a command, such as described above, MUST + result in the definition of a new Command Code. This subsequently + leads to the need to define a new Diameter application for any + application that will use that new command. + + The IANA considerations for Command Codes are discussed in + Section 3.1. +</pre> + +<p> +Creating new commands is an issue for the dictionary designer, not +diameter.</p> + +<pre> + +1.3.4. Creating New Diameter Applications + + Every Diameter application specification MUST have an IANA-assigned + Application Id (see Section 2.4). The managed Application ID space + is flat, and there is no relationship between different Diameter + applications with respect to their Application Ids. As such, there + is no versioning support provided by these Application Ids + themselves; every Diameter application is a standalone application. + If the application has a relationship with other Diameter + applications, such a relationship is not known to Diameter. +</pre> + +<p> +Creating new applications is an issue for the dictionary designer, +not diameter.</p> + +<p> +An application's Application Id is specified in the <c>@id</c> section +of a dictionary file.</p> + +<pre> + + Before describing the rules for creating new Diameter applications, + it is important to discuss the semantics of the AVP occurrences as + stated in the CCF and the M-bit flag (Section 4.1) for an AVP. There + is no relationship imposed between the two; they are set + independently. + + o The CCF indicates what AVPs are placed into a Diameter command by + the sender of that command. Often, since there are multiple modes + of protocol interactions, many of the AVPs are indicated as + optional. + + o The M-bit allows the sender to indicate to the receiver whether or + not understanding the semantics of an AVP and its content is + mandatory. If the M-bit is set by the sender and the receiver + does not understand the AVP or the values carried within that AVP, + then a failure is generated (see Section 7). +</pre> + +<p> +The M-bit is set on outgoing AVP's as directed by the relevant +dictionary. +For incoming AVP's, an M-bit set on an AVP that isn't +explicitly included in the definition of the command in question is +interpreted as a 5001 error, DIAMETER_AVP_UNSUPPORTED, the +consequences of which depend on the value of the &mod_application_opt; +<c>answer_errors</c> or <c>request_errors</c>.</p> + +<pre> + + It is the decision of the protocol designer when to develop a new + Diameter application rather than extending Diameter in other ways. + However, a new Diameter application MUST be created when one or more + of the following criteria are met: + + + + + + + +Fajardo, et al. Standards Track [Page 19] + +RFC 6733 Diameter Base Protocol October 2012 + + + M-bit Setting + + An AVP with the M-bit in the MUST column of the AVP flag table is + added to an existing Command/Application. An AVP with the M-bit + in the MAY column of the AVP flag table is added to an existing + Command/Application. + + Note: The M-bit setting for a given AVP is relevant to an + Application and each command within that application that includes + the AVP. That is, if an AVP appears in two commands for + application Foo and the M-bit settings are different in each + command, then there should be two AVP flag tables describing when + to set the M-bit. + + Commands + + A new command is used within the existing application because + either an additional command is added, an existing command has + been modified so that a new Command Code had to be registered, or + a command has been deleted. + + AVP Flag bits + + If an existing application changes the meaning/semantics of its + AVP Flags or adds new flag bits, then a new Diameter application + MUST be created. + + If the CCF definition of a command allows it, an implementation may + add arbitrary optional AVPs with the M-bit cleared (including vendor- + specific AVPs) to that command without needing to define a new + application. Please refer to Section 11.1.1 for details. +</pre> + +&nada; + +<pre> + +2. Protocol Overview + + The base Diameter protocol concerns itself with establishing + connections to peers, capabilities negotiation, how messages are sent + and routed through peers, and how the connections are eventually torn + down. The base protocol also defines certain rules that apply to all + message exchanges between Diameter nodes. + + Communication between Diameter peers begins with one peer sending a + message to another Diameter peer. The set of AVPs included in the + message is determined by a particular Diameter application. One AVP + that is included to reference a user's session is the Session-Id. + + The initial request for authentication and/or authorization of a user + would include the Session-Id AVP. The Session-Id is then used in all + subsequent messages to identify the user's session (see Section 8 for + + + +Fajardo, et al. Standards Track [Page 20] + +RFC 6733 Diameter Base Protocol October 2012 + + + more information). The communicating party may accept the request or + reject it by returning an answer message with the Result-Code AVP set + to indicate that an error occurred. The specific behavior of the + Diameter server or client receiving a request depends on the Diameter + application employed. + + Session state (associated with a Session-Id) MUST be freed upon + receipt of the Session-Termination-Request, Session-Termination- + Answer, expiration of authorized service time in the Session-Timeout + AVP, and according to rules established in a particular Diameter + application. +</pre> + +<p> +Like Session-Id, session state is maintained by the diameter user: +diameter has no session state of its own and does not interpret +STR/STA in any way.</p> + +<pre> + + The base Diameter protocol may be used by itself for accounting + applications. For authentication and authorization, it is always + extended for a particular application. + + Diameter clients MUST support the base protocol, which includes + accounting. In addition, they MUST fully support each Diameter + application that is needed to implement the client's service, e.g., + Network Access Server Requirements (NASREQ) [RFC2881] and/or Mobile + IPv4. A Diameter client MUST be referred to as "Diameter X Client" + where X is the application that it supports and not a "Diameter + Client". + + Diameter servers MUST support the base protocol, which includes + accounting. In addition, they MUST fully support each Diameter + application that is needed to implement the intended service, e.g., + NASREQ and/or Mobile IPv4. A Diameter server MUST be referred to as + "Diameter X Server" where X is the application that it supports, and + not a "Diameter Server". + + Diameter relays and redirect agents are transparent to the Diameter + applications, but they MUST support the Diameter base protocol, which + includes accounting, and all Diameter applications. + + Diameter proxies MUST support the base protocol, which includes + accounting. In addition, they MUST fully support each Diameter + application that is needed to implement proxied services, e.g., + NASREQ and/or Mobile IPv4. A Diameter proxy MUST be referred to as + "Diameter X Proxy" where X is the application which it supports, and + not a "Diameter Proxy". + +</pre> + +&nada; + +<pre> + + + + + + + + + +Fajardo, et al. Standards Track [Page 21] + +RFC 6733 Diameter Base Protocol October 2012 + + +2.1. Transport + + The Diameter Transport profile is defined in [RFC3539]. + + The base Diameter protocol is run on port 3868 for both TCP [RFC0793] + and SCTP [RFC4960]. For TLS [RFC5246] and Datagram Transport Layer + Security (DTLS) [RFC6347], a Diameter node that initiates a + connection prior to any message exchanges MUST run on port 5658. It + is assumed that TLS is run on top of TCP when it is used, and DTLS is + run on top of SCTP when it is used. +</pre> + +<p> +Which port a transport connects to or listens on is a matter of +configuration. +Both &man_tcp; and &man_sctp; will default to 3868 if no other value +is specified.</p> + +<pre> + + If the Diameter peer does not support receiving TLS/TCP and DTLS/SCTP + connections on port 5658 (i.e., the peer complies only with RFC + 3588), then the initiator MAY revert to using TCP or SCTP on port + 3868. Note that this scheme is kept only for the purpose of backward + compatibility and that there are inherent security vulnerabilities + when the initial CER/CEA messages are sent unprotected (see + Section 5.6). + + Diameter clients MUST support either TCP or SCTP; agents and servers + SHOULD support both. + + A Diameter node MAY initiate connections from a source port other + than the one that it declares it accepts incoming connections on, and + it MUST always be prepared to receive connections on port 3868 for + TCP or SCTP and port 5658 for TLS/TCP and DTLS/SCTP connections. + When DNS-based peer discovery (Section 5.2) is used, the port numbers + received from SRV records take precedence over the default ports + (3868 and 5658). + + A given Diameter instance of the peer state machine MUST NOT use more + than one transport connection to communicate with a given peer, + unless multiple instances exist on the peer, in which, case a + separate connection per process is allowed. +</pre> + +<p> +The &mod_service_opt; <c>restrict_connection</c> controls to what +extent a diameter service allows multiple connections to the same +peer. +(As identified by the value of Origin-Host received from it +during capabilities exchange.)</p> + +<pre> + + When no transport connection exists with a peer, an attempt to + connect SHOULD be made periodically. This behavior is handled via + the Tc timer (see Section 12 for details), whose recommended value is + 30 seconds. There are certain exceptions to this rule, such as when + a peer has terminated the transport connection stating that it does + not wish to communicate. + +</pre> + +<p> +The frequency of reconnection attempts is configured with the +&mod_transport_opt; <c>reconnect_timer</c> and +<c>watchdog_timer</c>.</p> + +<pre> + + When connecting to a peer and either zero or more transports are + specified, TLS SHOULD be tried first, followed by DTLS, then by TCP, + and finally by SCTP. See Section 5.2 for more information on peer + discovery. +</pre> + +<p> +The order in which different transports are attempted depends on the +order of &mod_transport_opt; <c>transport_module</c> and +<c>transport_config</c> tuples in transport configuration.</p> + +<pre> + + + +Fajardo, et al. Standards Track [Page 22] + +RFC 6733 Diameter Base Protocol October 2012 + + + Diameter implementations SHOULD be able to interpret ICMP protocol + port unreachable messages as explicit indications that the server is + not reachable, subject to security policy on trusting such messages. + Further guidance regarding the treatment of ICMP errors can be found + in [RFC5927] and [RFC5461]. Diameter implementations SHOULD also be + able to interpret a reset from the transport and timed-out connection + attempts. If Diameter receives data from the lower layer that cannot + be parsed or identified as a Diameter error made by the peer, the + stream is compromised and cannot be recovered. The transport + connection MUST be closed using a RESET call (send a TCP RST bit) or + an SCTP ABORT message (graceful closure is compromised). +</pre> + +<p> +ICMP messages and other transport-level errors aren't directly +visible to diameter but transport implementations like &man_tcp; and +&man_sctp; propagate these as terminating transport processes.</p> + +<pre> + +2.1.1. SCTP Guidelines + + Diameter messages SHOULD be mapped into SCTP streams in a way that + avoids head-of-the-line (HOL) blocking. Among different ways of + performing the mapping that fulfill this requirement it is + RECOMMENDED that a Diameter node send every Diameter message (request + or response) over stream zero with the unordered flag set. However, + Diameter nodes MAY select and implement other design alternatives for + avoiding HOL blocking such as using multiple streams with the + unordered flag cleared (as originally instructed in RFC 3588). On + the receiving side, a Diameter entity MUST be ready to receive + Diameter messages over any stream, and it is free to return responses + over a different stream. This way, both sides manage the available + streams in the sending direction, independently of the streams chosen + by the other side to send a particular Diameter message. These + messages can be out-of-order and belong to different Diameter + sessions. +</pre> + +<p> +&man_sctp; allows the sender to specify a stream number explicitly. +The stream on which an incoming message is received it passed to +&app_handle_request; and &app_handle_answer; callbacks as +<c>transport_data</c> in a <c>#diameter_packet{}</c>.</p> + +<p> +Ordered or unordered delivery can be configured per transport.</p> + +<pre> + + Out-of-order delivery has special concerns during a connection + establishment and termination. When a connection is established, the + responder side sends a CEA message and moves to R-Open state as + specified in Section 5.6. If an application message is sent shortly + after the CEA and delivered out-of-order, the initiator side, still + in Wait-I-CEA state, will discard the application message and close + the connection. In order to avoid this race condition, the receiver + side SHOULD NOT use out-of-order delivery methods until the first + message has been received from the initiator, proving that it has + moved to I-Open state. To trigger such a message, the receiver side + could send a DWR immediately after sending a CEA. Upon reception of + the corresponding DWA, the receiver side should start using out-of- + order delivery methods to counter the HOL blocking. +</pre> + +<p> +&man_sctp; does not currently allow the user to switch between ordered +and unordered delivery, or to specify the manner of sending per +message: one or the other must be configured, the defaults being +ordered.</p> + +<pre> + + Another race condition may occur when DPR and DPA messages are used. + Both DPR and DPA are small in size; thus, they may be delivered to + the peer faster than application messages when an out-of-order + delivery mechanism is used. Therefore, it is possible that a DPR/DPA + + + +Fajardo, et al. Standards Track [Page 23] + +RFC 6733 Diameter Base Protocol October 2012 + + + exchange completes while application messages are still in transit, + resulting in a loss of these messages. An implementation could + mitigate this race condition, for example, using timers, and wait for + a short period of time for pending application level messages to + arrive before proceeding to disconnect the transport connection. + Eventually, lost messages are handled by the retransmission mechanism + described in Section 5.5.4. + + A Diameter agent SHOULD use dedicated payload protocol identifiers + (PPIDs) for clear text and encrypted SCTP DATA chunks instead of only + using the unspecified payload protocol identifier (value 0). For + this purpose, two PPID values are allocated: the PPID value 46 is for + Diameter messages in clear text SCTP DATA chunks, and the PPID value + 47 is for Diameter messages in protected DTLS/SCTP DATA chunks. +</pre> + +&nada; + +<pre> + +2.2. Securing Diameter Messages + + Connections between Diameter peers SHOULD be protected by TLS/TCP and + DTLS/SCTP. All Diameter base protocol implementations MUST support + the use of TLS/TCP and DTLS/SCTP. If desired, alternative security + mechanisms that are independent of Diameter, such as IPsec [RFC4301], + can be deployed to secure connections between peers. The Diameter + protocol MUST NOT be used without one of TLS, DTLS, or IPsec. +</pre> + +<p> +As noted above, DTLS is not currently supported and IPsec usage is +transparent to diameter. +Security is not enforced by diameter.</p> + +<pre> + +2.3. Diameter Application Compliance + + Application Ids are advertised during the capabilities exchange phase + (see Section 5.3). Advertising support of an application implies + that the sender supports the functionality specified in the + respective Diameter application specification. + + Implementations MAY add arbitrary optional AVPs with the M-bit + cleared (including vendor-specific AVPs) to a command defined in an + application, but only if the command's CCF syntax specification + allows for it. Please refer to Section 11.1.1 for details. +</pre> + +&nada; + +<pre> + +2.4. Application Identifiers + + Each Diameter application MUST have an IANA-assigned Application ID. + The base protocol does not require an Application Id since its + support is mandatory. During the capabilities exchange, Diameter + nodes inform their peers of locally supported applications. + Furthermore, all Diameter messages contain an Application Id, which + is used in the message forwarding process. + + + + + + + +Fajardo, et al. Standards Track [Page 24] + +RFC 6733 Diameter Base Protocol October 2012 + + + The following Application Id values are defined: + + Diameter common message 0 + Diameter base accounting 3 + Relay 0xffffffff +</pre> + +<p> +These applications are implemented in the dictionary modules +<c>diameter_gen_base_rfc6733</c>, <c>diameter_gen_acct_rfc6733</c> and +<c>diameter_relay</c> respectively. +There are also RFC 3588 versions or the common and accounting +dictionaries: <c>diameter_gen_base_rfc3588</c> and +<c>diameter_base_accounting</c>. +(The inconsistent naming is historical.) +Dictionary modules are configured using the &mod_application_opt; +<c>dictionary</c>.</p> + +<pre> + Relay and redirect agents MUST advertise the Relay Application ID, + while all other Diameter nodes MUST advertise locally supported + applications. The receiver of a Capabilities Exchange message + advertising relay service MUST assume that the sender supports all + current and future applications. + + Diameter relay and proxy agents are responsible for finding an + upstream server that supports the application of a particular + message. If none can be found, an error message is returned with the + Result-Code AVP set to DIAMETER_UNABLE_TO_DELIVER. +</pre> + +&nada; + +<pre> + +2.5. Connections vs. Sessions + + This section attempts to provide the reader with an understanding of + the difference between "connection" and "session", which are terms + used extensively throughout this document. + + A connection refers to a transport-level connection between two peers + that is used to send and receive Diameter messages. A session is a + logical concept at the application layer that exists between the + Diameter client and the Diameter server; it is identified via the + Session-Id AVP. + + +--------+ +-------+ +--------+ + | Client | | Relay | | Server | + +--------+ +-------+ +--------+ + <----------> <----------> + peer connection A peer connection B + + <-----------------------------> + User session x + + Figure 1: Diameter Connections and Sessions + + In the example provided in Figure 1, peer connection A is established + between the client and the relay. Peer connection B is established + between the relay and the server. User session X spans from the + client via the relay to the server. Each "user" of a service causes + an auth request to be sent, with a unique session identifier. Once + accepted by the server, both the client and the server are aware of + the session. + + + + +Fajardo, et al. Standards Track [Page 25] + +RFC 6733 Diameter Base Protocol October 2012 + + + It is important to note that there is no relationship between a + connection and a session, and that Diameter messages for multiple + sessions are all multiplexed through a single connection. Also, note + that Diameter messages pertaining to the session, both application- + specific and those that are defined in this document such as ASR/ASA, + RAR/RAA, and STR/STA, MUST carry the Application Id of the + application. Diameter messages pertaining to peer connection + establishment and maintenance such as CER/CEA, DWR/DWA, and DPR/DPA + MUST carry an Application Id of zero (0). +</pre> + +<p> +As noted above, diameter is not involved in session management. +This is the responsibility of the diameter user.</p> + +<pre> + +2.6. Peer Table + + The Diameter peer table is used in message forwarding and is + referenced by the routing table. A peer table entry contains the + following fields: + + Host Identity + + Following the conventions described for the DiameterIdentity- + derived AVP data format in Section 4.3.1, this field contains the + contents of the Origin-Host (Section 6.3) AVP found in the CER or + CEA message. + + StatusT + + This is the state of the peer entry, and it MUST match one of the + values listed in Section 5.6. + + Static or Dynamic + + Specifies whether a peer entry was statically configured or + dynamically discovered. + + Expiration Time + + Specifies the time at which dynamically discovered peer table + entries are to be either refreshed or expired. If public key + certificates are used for Diameter security (e.g., with TLS), this + value MUST NOT be greater than the expiry times in the relevant + certificates. + + TLS/TCP and DTLS/SCTP Enabled + + Specifies whether TLS/TCP and DTLS/SCTP is to be used when + communicating with the peer. + + Additional security information, when needed (e.g., keys, + certificates). +</pre> + +<p> +The Peer Table is not directly accessible to the diameter user. +Information about connected peers can be retrieved using +&mod_service_info;.</p> + +<pre> + + + +Fajardo, et al. Standards Track [Page 26] + +RFC 6733 Diameter Base Protocol October 2012 + + +2.7. Routing Table + + All Realm-Based routing lookups are performed against what is + commonly known as the routing table (see Section 12). Each routing + table entry contains the following fields: + + Realm Name + + This is the field that MUST be used as a primary key in the + routing table lookups. Note that some implementations perform + their lookups based on longest-match-from-the-right on the realm + rather than requiring an exact match. + + Application Identifier + + An application is identified by an Application Id. A route entry + can have a different destination based on the Application Id in + the message header. This field MUST be used as a secondary key + field in routing table lookups. + + Local Action + + The Local Action field is used to identify how a message should be + treated. The following actions are supported: + + 1. LOCAL - Diameter messages that can be satisfied locally and do + not need to be routed to another Diameter entity. + + 2. RELAY - All Diameter messages that fall within this category + MUST be routed to a next-hop Diameter entity that is indicated + by the identifier described below. Routing is done without + modifying any non-routing AVPs. See Section 6.1.9 for + relaying guidelines. + + 3. PROXY - All Diameter messages that fall within this category + MUST be routed to a next Diameter entity that is indicated by + the identifier described below. The local server MAY apply + its local policies to the message by including new AVPs to the + message prior to routing. See Section 6.1.9 for proxying + guidelines. + + 4. REDIRECT - Diameter messages that fall within this category + MUST have the identity of the home Diameter server(s) + appended, and returned to the sender of the message. See + Section 6.1.8 for redirection guidelines. + + + + + + +Fajardo, et al. Standards Track [Page 27] + +RFC 6733 Diameter Base Protocol October 2012 + + + Server Identifier + + The identity of one or more servers to which the message is to be + routed. This identity MUST also be present in the Host Identity + field of the peer table (Section 2.6). When the Local Action is + set to RELAY or PROXY, this field contains the identity of the + server(s) to which the message MUST be routed. When the Local + Action field is set to REDIRECT, this field contains the identity + of one or more servers to which the message MUST be redirected. + + Static or Dynamic + + Specifies whether a route entry was statically configured or + dynamically discovered. + + Expiration Time + + Specifies the time at which a dynamically discovered route table + entry expires. If public key certificates are used for Diameter + security (e.g., with TLS), this value MUST NOT be greater than the + expiry time in the relevant certificates. + + It is important to note that Diameter agents MUST support at least + one of the LOCAL, RELAY, PROXY, or REDIRECT modes of operation. + Agents do not need to support all modes of operation in order to + conform with the protocol specification, but they MUST follow the + protocol compliance guidelines in Section 2. Relay agents and + proxies MUST NOT reorder AVPs. + + The routing table MAY include a default entry that MUST be used for + any requests not matching any of the other entries. The routing + table MAY consist of only such an entry. + + When a request is routed, the target server MUST have advertised the + Application Id (see Section 2.4) for the given message or have + advertised itself as a relay or proxy agent. Otherwise, an error is + returned with the Result-Code AVP set to DIAMETER_UNABLE_TO_DELIVER. +</pre> + +<p> +Routing does not need specific support in diameter: a user can +maintain their own routing table if desired and implement any desired +routing in &man_app; callbacks. +However, it may be convenient to add more specific routing support to +diameter in the future.</p> + +<pre> + +2.8. Role of Diameter Agents + + In addition to clients and servers, the Diameter protocol introduces + relay, proxy, redirect, and translation agents, each of which is + defined in Section 1.2. Diameter agents are useful for several + reasons: +</pre> + +<p> +An noted above, the role a node plays is largely a question of +configuration and &man_app; callback implementation.</p> + +<pre> + + o They can distribute administration of systems to a configurable + grouping, including the maintenance of security associations. + + + + +Fajardo, et al. Standards Track [Page 28] + +RFC 6733 Diameter Base Protocol October 2012 + + + o They can be used for concentration of requests from a number of + co-located or distributed NAS equipment sets to a set of like user + groups. + + o They can do value-added processing to the requests or responses. + + o They can be used for load balancing. + + o A complex network will have multiple authentication sources, they + can sort requests and forward towards the correct target. + + The Diameter protocol requires that agents maintain transaction + state, which is used for failover purposes. Transaction state + implies that upon forwarding a request, its Hop-by-Hop Identifier is + saved; the field is replaced with a locally unique identifier, which + is restored to its original value when the corresponding answer is + received. The request's state is released upon receipt of the + answer. A stateless agent is one that only maintains transaction + state. + + The Proxy-Info AVP allows stateless agents to add local state to a + Diameter request, with the guarantee that the same state will be + present in the answer. However, the protocol's failover procedures + require that agents maintain a copy of pending requests. + + A stateful agent is one that maintains session state information by + keeping track of all authorized active sessions. Each authorized + session is bound to a particular service, and its state is considered + active until either the agent is notified otherwise or the session + expires. Each authorized session has an expiration, which is + communicated by Diameter servers via the Session-Timeout AVP. + + Maintaining session state may be useful in certain applications, such + as: + + o Protocol translation (e.g., RADIUS <-> Diameter) + + o Limiting resources authorized to a particular user + + o Per-user or per-transaction auditing + + A Diameter agent MAY act in a stateful manner for some requests and + be stateless for others. A Diameter implementation MAY act as one + type of agent for some requests and as another type of agent for + others. +</pre> + +&nada; + +<pre> + + + + + + +Fajardo, et al. Standards Track [Page 29] + +RFC 6733 Diameter Base Protocol October 2012 + + +2.8.1. Relay Agents + + Relay agents are Diameter agents that accept requests and route + messages to other Diameter nodes based on information found in the + messages (e.g., the value of the Destination-Realm AVP Section 6.6). + This routing decision is performed using a list of supported realms + and known peers. This is known as the routing table, as is defined + further in Section 2.7. + + Relays may, for example, be used to aggregate requests from multiple + Network Access Servers (NASes) within a common geographical area + (Point of Presence, POP). The use of relays is advantageous since it + eliminates the need for NASes to be configured with the necessary + security information they would otherwise require to communicate with + Diameter servers in other realms. Likewise, this reduces the + configuration load on Diameter servers that would otherwise be + necessary when NASes are added, changed, or deleted. + + Relays modify Diameter messages by inserting and removing routing + information, but they do not modify any other portion of a message. + Relays SHOULD NOT maintain session state but MUST maintain + transaction state. + + +------+ ---------> +------+ ---------> +------+ + | | 1. Request | | 2. Request | | + | NAS | | DRL | | HMS | + | | 4. Answer | | 3. Answer | | + +------+ <--------- +------+ <--------- +------+ + example.net example.net example.com + + Figure 2: Relaying of Diameter messages + + The example provided in Figure 2 depicts a request issued from a NAS, + which is an access device, for the user [email protected]. Prior to + issuing the request, the NAS performs a Diameter route lookup, using + "example.com" as the key, and determines that the message is to be + relayed to a DRL, which is a Diameter relay. The DRL performs the + same route lookup as the NAS, and relays the message to the HMS, + which is example.com's home server. The HMS identifies that the + request can be locally supported (via the realm), processes the + authentication and/or authorization request, and replies with an + answer, which is routed back to the NAS using saved transaction + state. + + Since relays do not perform any application-level processing, they + provide relaying services for all Diameter applications; therefore, + they MUST advertise the Relay Application Id. +</pre> + +<p> +Requests are relayed by returning a <c>relay</c> tuple from a +&app_handle_request; callback.</p> + +<pre> + + + +Fajardo, et al. Standards Track [Page 30] + +RFC 6733 Diameter Base Protocol October 2012 + + +2.8.2. Proxy Agents + + Similar to relays, proxy agents route Diameter messages using the + Diameter routing table. However, they differ since they modify + messages to implement policy enforcement. This requires that proxies + maintain the state of their downstream peers (e.g., access devices) + to enforce resource usage, provide admission control, and provide + provisioning. + + Proxies may, for example, be used in call control centers or access + ISPs that provide outsourced connections; they can monitor the number + and type of ports in use and make allocation and admission decisions + according to their configuration. + + Since enforcing policies requires an understanding of the service + being provided, proxies MUST only advertise the Diameter applications + they support. +</pre> + +&nada; + +<pre> + +2.8.3. Redirect Agents + + Redirect agents are useful in scenarios where the Diameter routing + configuration needs to be centralized. An example is a redirect + agent that provides services to all members of a consortium, but does + not wish to be burdened with relaying all messages between realms. + This scenario is advantageous since it does not require that the + consortium provide routing updates to its members when changes are + made to a member's infrastructure. + + Since redirect agents do not relay messages, and only return an + answer with the information necessary for Diameter agents to + communicate directly, they do not modify messages. Since redirect + agents do not receive answer messages, they cannot maintain session + state. + + The example provided in Figure 3 depicts a request issued from the + access device, NAS, for the user [email protected]. The message is + forwarded by the NAS to its relay, DRL, which does not have a routing + entry in its Diameter routing table for example.com. The DRL has a + default route configured to DRD, which is a redirect agent that + returns a redirect notification to DRL, as well as the HMS' contact + information. Upon receipt of the redirect notification, the DRL + establishes a transport connection with the HMS, if one doesn't + already exist, and forwards the request to it. + + + + + + + + +Fajardo, et al. Standards Track [Page 31] + +RFC 6733 Diameter Base Protocol October 2012 + + + +------+ + | | + | DRD | + | | + +------+ + ^ | + 2. Request | | 3. Redirection + | | Notification + | v + +------+ ---------> +------+ ---------> +------+ + | | 1. Request | | 4. Request | | + | NAS | | DRL | | HMS | + | | 6. Answer | | 5. Answer | | + +------+ <--------- +------+ <--------- +------+ + example.net example.net example.com + + Figure 3: Redirecting a Diameter Message + + Since redirect agents do not perform any application-level + processing, they provide relaying services for all Diameter + applications; therefore, they MUST advertise the Relay Application + ID. +</pre> + +&nada; + +<pre> + +2.8.4. Translation Agents + + A translation agent is a device that provides translation between two + protocols (e.g., RADIUS<->Diameter, TACACS+<->Diameter). Translation + agents are likely to be used as aggregation servers to communicate + with a Diameter infrastructure, while allowing for the embedded + systems to be migrated at a slower pace. + + Given that the Diameter protocol introduces the concept of long-lived + authorized sessions, translation agents MUST be session stateful and + MUST maintain transaction state. + + Translation of messages can only occur if the agent recognizes the + application of a particular request; therefore, translation agents + MUST only advertise their locally supported applications. + + +------+ ---------> +------+ ---------> +------+ + | | RADIUS Request | | Diameter Request | | + | NAS | | TLA | | HMS | + | | RADIUS Answer | | Diameter Answer | | + +------+ <--------- +------+ <--------- +------+ + example.net example.net example.com + + Figure 4: Translation of RADIUS to Diameter +</pre> + +&nada; + +<pre> + + + + +Fajardo, et al. Standards Track [Page 32] + +RFC 6733 Diameter Base Protocol October 2012 + + +2.9. Diameter Path Authorization + + As noted in Section 2.2, Diameter provides transmission-level + security for each connection using TLS/TCP and DTLS/SCTP. Therefore, + each connection can be authenticated and can be replay and integrity + protected. + + In addition to authenticating each connection, the entire session + MUST also be authorized. Before initiating a connection, a Diameter + peer MUST check that its peers are authorized to act in their roles. + For example, a Diameter peer may be authentic, but that does not mean + that it is authorized to act as a Diameter server advertising a set + of Diameter applications. + + Prior to bringing up a connection, authorization checks are performed + at each connection along the path. Diameter capabilities negotiation + (CER/CEA) also MUST be carried out, in order to determine what + Diameter applications are supported by each peer. Diameter sessions + MUST be routed only through authorized nodes that have advertised + support for the Diameter application required by the session. + + As noted in Section 6.1.9, a relay or proxy agent MUST append a + Route-Record AVP to all requests forwarded. The AVP contains the + identity of the peer from which the request was received. + + The home Diameter server, prior to authorizing a session, MUST check + the Route-Record AVPs to make sure that the route traversed by the + request is acceptable. For example, administrators within the home + realm may not wish to honor requests that have been routed through an + untrusted realm. By authorizing a request, the home Diameter server + is implicitly indicating its willingness to engage in the business + transaction as specified by any contractual relationship between the + server and the previous hop. A DIAMETER_AUTHORIZATION_REJECTED error + message (see Section 7.1.5) is sent if the route traversed by the + request is unacceptable. + + A home realm may also wish to check that each accounting request + message corresponds to a Diameter response authorizing the session. + Accounting requests without corresponding authorization responses + SHOULD be subjected to further scrutiny, as should accounting + requests indicating a difference between the requested and provided + service. + + Forwarding of an authorization response is considered evidence of a + willingness to take on financial risk relative to the session. A + local realm may wish to limit this exposure, for example, by + establishing credit limits for intermediate realms and refusing to + accept responses that would violate those limits. By issuing an + + + +Fajardo, et al. Standards Track [Page 33] + +RFC 6733 Diameter Base Protocol October 2012 + + + accounting request corresponding to the authorization response, the + local realm implicitly indicates its agreement to provide the service + indicated in the authorization response. If the service cannot be + provided by the local realm, then a DIAMETER_UNABLE_TO_COMPLY error + message MUST be sent within the accounting request; a Diameter client + receiving an authorization response for a service that it cannot + perform MUST NOT substitute an alternate service and then send + accounting requests for the alternate service instead. +</pre> + +&nada; + +<pre> + +3. Diameter Header + + A summary of the Diameter header format is shown below. The fields + are transmitted in network byte order. + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Version | Message Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Command Flags | Command Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Application-ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Hop-by-Hop Identifier | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | End-to-End Identifier | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AVPs ... + +-+-+-+-+-+-+-+-+-+-+-+-+- +</pre> + +<p> +The Diameter Header is represented by the <c>diameter_header</c> +record defined in <c>diameter.hrl</c>. +The <c>diameter_packet</c> record contains a <c>header</c> field whose +value will be a decoded <c>#diameter_header{}</c> for incoming +messages passed to &app_handle_request; and &app_handle_answer; +callbacks. +In the case of outgoing messages, diameter and the relevant +dictionary populate the Diameter Header appropriately, although +&app_prepare_request; and &app_handle_request; callbacks can modify +header values. +(Which can be useful in test.)</p> + +<pre> + + Version + + This Version field MUST be set to 1 to indicate Diameter Version + 1. + + Message Length + + The Message Length field is three octets and indicates the length + of the Diameter message including the header fields and the padded + AVPs. Thus, the Message Length field is always a multiple of 4. + + Command Flags + + The Command Flags field is eight bits. The following bits are + assigned: + + + + + + +Fajardo, et al. Standards Track [Page 34] + +RFC 6733 Diameter Base Protocol October 2012 + + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |R P E T r r r r| + +-+-+-+-+-+-+-+-+ + + R(equest) + + If set, the message is a request. If cleared, the message is + an answer. + + P(roxiable) + + If set, the message MAY be proxied, relayed, or redirected. If + cleared, the message MUST be locally processed. + + E(rror) + + If set, the message contains a protocol error, and the message + will not conform to the CCF described for this command. + Messages with the 'E' bit set are commonly referred to as error + messages. This bit MUST NOT be set in request messages (see + Section 7.2). + + T(Potentially retransmitted message) + + This flag is set after a link failover procedure, to aid the + removal of duplicate requests. It is set when resending + requests not yet acknowledged, as an indication of a possible + duplicate due to a link failure. This bit MUST be cleared when + sending a request for the first time; otherwise, the sender + MUST set this flag. Diameter agents only need to be concerned + about the number of requests they send based on a single + received request; retransmissions by other entities need not be + tracked. Diameter agents that receive a request with the T + flag set, MUST keep the T flag set in the forwarded request. + This flag MUST NOT be set if an error answer message (e.g., a + protocol error) has been received for the earlier message. It + can be set only in cases where no answer has been received from + the server for a request, and the request has been sent again. + This flag MUST NOT be set in answer messages. + + r(eserved) + + These flag bits are reserved for future use; they MUST be set + to zero and ignored by the receiver. +</pre> + +<p> +Reserved bits are set to 0 in outgoing messages.</p> + +<pre> + + + + + + +Fajardo, et al. Standards Track [Page 35] + +RFC 6733 Diameter Base Protocol October 2012 + + + Command Code + + The Command Code field is three octets and is used in order to + communicate the command associated with the message. The 24-bit + address space is managed by IANA (see Section 3.1). Command Code + values 16,777,214 and 16,777,215 (hexadecimal values FFFFFE- + FFFFFF) are reserved for experimental use (see Section 11.2). + + Application-ID + + Application-ID is four octets and is used to identify for which + application the message is applicable. The application can be an + authentication application, an accounting application, or a + vendor-specific application. + + The value of the Application-ID field in the header MUST be the + same as any relevant Application-Id AVPs contained in the message. + + Hop-by-Hop Identifier + + The Hop-by-Hop Identifier is an unsigned 32-bit integer field (in + network byte order) that aids in matching requests and replies. + The sender MUST ensure that the Hop-by-Hop Identifier in a request + is unique on a given connection at any given time, and it MAY + attempt to ensure that the number is unique across reboots. The + sender of an answer message MUST ensure that the Hop-by-Hop + Identifier field contains the same value that was found in the + corresponding request. The Hop-by-Hop Identifier is normally a + monotonically increasing number, whose start value was randomly + generated. An answer message that is received with an unknown + Hop-by-Hop Identifier MUST be discarded. + + End-to-End Identifier + + The End-to-End Identifier is an unsigned 32-bit integer field (in + network byte order) that is used to detect duplicate messages. + Upon reboot, implementations MAY set the high order 12 bits to + contain the low order 12 bits of current time, and the low order + 20 bits to a random value. Senders of request messages MUST + insert a unique identifier on each message. The identifier MUST + remain locally unique for a period of at least 4 minutes, even + across reboots. The originator of an answer message MUST ensure + that the End-to-End Identifier field contains the same value that + was found in the corresponding request. The End-to-End Identifier + MUST NOT be modified by Diameter agents of any kind. The + combination of the Origin-Host AVP (Section 6.3) and this field is + used to detect duplicates. Duplicate requests SHOULD cause the + same answer to be transmitted (modulo the Hop-by-Hop Identifier + + + +Fajardo, et al. Standards Track [Page 36] + +RFC 6733 Diameter Base Protocol October 2012 + + + field and any routing AVPs that may be present), and they MUST NOT + affect any state that was set when the original request was + processed. Duplicate answer messages that are to be locally + consumed (see Section 6.2) SHOULD be silently discarded. + + AVPs + + AVPs are a method of encapsulating information relevant to the + Diameter message. See Section 4 for more information on AVPs. +</pre> + +&nada; + +<pre> + +3.1. Command Codes + + Each command Request/Answer pair is assigned a Command Code, and the + sub-type (i.e., request or answer) is identified via the 'R' bit in + the Command Flags field of the Diameter header. + + Every Diameter message MUST contain a Command Code in its header's + Command Code field, which is used to determine the action that is to + be taken for a particular message. The following Command Codes are + defined in the Diameter base protocol: + + Section + Command Name Abbrev. Code Reference + -------------------------------------------------------- + Abort-Session-Request ASR 274 8.5.1 + Abort-Session-Answer ASA 274 8.5.2 + Accounting-Request ACR 271 9.7.1 + Accounting-Answer ACA 271 9.7.2 + Capabilities-Exchange- CER 257 5.3.1 + Request + Capabilities-Exchange- CEA 257 5.3.2 + Answer + Device-Watchdog-Request DWR 280 5.5.1 + Device-Watchdog-Answer DWA 280 5.5.2 + Disconnect-Peer-Request DPR 282 5.4.1 + Disconnect-Peer-Answer DPA 282 5.4.2 + Re-Auth-Request RAR 258 8.3.1 + Re-Auth-Answer RAA 258 8.3.2 + Session-Termination- STR 275 8.4.1 + Request + Session-Termination- STA 275 8.4.2 + Answer +</pre> + +<p> +These messages are all defined in diameter's implementation of the +common dictionary in modules <c>diameter_gen_base_rfc6733</c> and +<c>diameter_gen_base_rfc3588</c>. +Corresponding record definitions are found in +<c>diameter_gen_base_rfc6733.hrl</c> and +<c>diameter_gen_base_rfc3588.hrl</c>.</p> + +<pre> + + + + + + + + + +Fajardo, et al. Standards Track [Page 37] + +RFC 6733 Diameter Base Protocol October 2012 + + +3.2. Command Code Format Specification + + Every Command Code defined MUST include a corresponding Command Code + Format (CCF) specification, which is used to define the AVPs that + MUST or MAY be present when sending the message. The following ABNF + specifies the CCF used in the definition: +</pre> + +<p> +The CCF is what is specified in the <c>@messages</c> section of the +&man_dict; format, except as noted below.</p> + +<pre> + + command-def = "<" command-name ">" "::=" diameter-message +</pre> + +<p> +Angle brackets are currently not allowed here. +This was a change between RFC 3588 and RFC 6733: the former disallowed +them in the grammar but included them in its own command definitions.</p> + +<pre> + + command-name = diameter-name + + diameter-name = ALPHA *(ALPHA / DIGIT / "-") + + diameter-message = header *fixed *required *optional + + header = "<Diameter-Header:" command-id + [r-bit] [p-bit] [e-bit] [application-id]">" + + application-id = 1*DIGIT + + command-id = 1*DIGIT + ; The Command Code assigned to the command. + + r-bit = ", REQ" + ; If present, the 'R' bit in the Command + ; Flags is set, indicating that the message + ; is a request as opposed to an answer. + + p-bit = ", PXY" + ; If present, the 'P' bit in the Command + ; Flags is set, indicating that the message + ; is proxiable. + + e-bit = ", ERR" + ; If present, the 'E' bit in the Command + ; Flags is set, indicating that the answer + ; message contains a Result-Code AVP in + ; the "protocol error" class. + + fixed = [qual] "<" avp-spec ">" + ; Defines the fixed position of an AVP. + + required = [qual] "{" avp-spec "}" + ; The AVP MUST be present and can appear + ; anywhere in the message. + + + + + + +Fajardo, et al. Standards Track [Page 38] + +RFC 6733 Diameter Base Protocol October 2012 + + + optional = [qual] "[" avp-name "]" + ; The avp-name in the 'optional' rule cannot + ; evaluate to any AVP Name that is included + ; in a fixed or required rule. The AVP can + ; appear anywhere in the message. + ; + ; NOTE: "[" and "]" have a slightly different + ; meaning than in ABNF. These braces + ; cannot be used to express optional fixed rules + ; (such as an optional ICV at the end). To do + ; this, the convention is '0*1fixed'. + + qual = [min] "*" [max] + ; See ABNF conventions, RFC 5234, Section 4. + ; The absence of any qualifier depends on + ; whether it precedes a fixed, required, or + ; optional rule. If a fixed or required rule has + ; no qualifier, then exactly one such AVP MUST + ; be present. If an optional rule has no + ; qualifier, then 0 or 1 such AVP may be + ; present. If an optional rule has a qualifier, + ; then the value of min MUST be 0 if present. + + min = 1*DIGIT + ; The minimum number of times the element may + ; be present. If absent, the default value is 0 + ; for fixed and optional rules and 1 for + ; required rules. The value MUST be at least 1 + ; for required rules. + + max = 1*DIGIT + ; The maximum number of times the element may + ; be present. If absent, the default value is + ; infinity. A value of 0 implies the AVP MUST + ; NOT be present. + + avp-spec = diameter-name + ; The avp-spec has to be an AVP Name, defined + ; in the base or extended Diameter + ; specifications. + + avp-name = avp-spec / "AVP" + ; The string "AVP" stands for *any* arbitrary AVP + ; Name, not otherwise listed in that Command Code + ; definition. The inclusion of this string + ; is recommended for all CCFs to allow for + ; extensibility. + + + + +Fajardo, et al. Standards Track [Page 39] + +RFC 6733 Diameter Base Protocol October 2012 + + + The following is a definition of a fictitious Command Code: + + Example-Request ::= < Diameter Header: 9999999, REQ, PXY > + { User-Name } + 1* { Origin-Host } + * [ AVP ] +</pre> + +&nada; + +<pre> + +3.3. Diameter Command Naming Conventions + + Diameter command names typically includes one or more English words + followed by the verb "Request" or "Answer". Each English word is + delimited by a hyphen. A three-letter acronym for both the request + and answer is also normally provided. + + An example is a message set used to terminate a session. The command + name is Session-Terminate-Request and Session-Terminate-Answer, while + the acronyms are STR and STA, respectively. + + Both the request and the answer for a given command share the same + Command Code. The request is identified by the R(equest) bit in the + Diameter header set to one (1), to ask that a particular action be + performed, such as authorizing a user or terminating a session. Once + the receiver has completed the request, it issues the corresponding + answer, which includes a result code that communicates one of the + following: + + o The request was successful + + o The request failed + + o An additional request has to be sent to provide information the + peer requires prior to returning a successful or failed answer. + + o The receiver could not process the request, but provides + information about a Diameter peer that is able to satisfy the + request, known as redirect. + + Additional information, encoded within AVPs, may also be included in + answer messages. +</pre> + +<p> +The &man_dict; format places no requirement on the naming of commands.</p> + +<pre> + +4. Diameter AVPs + + Diameter AVPs carry specific authentication, accounting, + authorization, and routing information as well as configuration + details for the request and reply. + + + + + + +Fajardo, et al. Standards Track [Page 40] + +RFC 6733 Diameter Base Protocol October 2012 + + + Each AVP of type OctetString MUST be padded to align on a 32-bit + boundary, while other AVP types align naturally. A number of zero- + valued bytes are added to the end of the AVP Data field until a word + boundary is reached. The length of the padding is not reflected in + the AVP Length field. + +4.1. AVP Header + + The fields in the AVP header MUST be sent in network byte order. The + format of the header is: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AVP Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V M P r r r r r| AVP Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Vendor-ID (opt) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data ... + +-+-+-+-+-+-+-+-+ + + AVP Code + + The AVP Code, combined with the Vendor-Id field, identifies the + attribute uniquely. AVP numbers 1 through 255 are reserved for + reuse of RADIUS attributes, without setting the Vendor-Id field. + AVP numbers 256 and above are used for Diameter, which are + allocated by IANA (see Section 11.1.1). + + AVP Flags + + The AVP Flags field informs the receiver how each attribute must + be handled. New Diameter applications SHOULD NOT define + additional AVP Flag bits. However, note that new Diameter + applications MAY define additional bits within the AVP header, and + an unrecognized bit SHOULD be considered an error. The sender of + the AVP MUST set 'R' (reserved) bits to 0 and the receiver SHOULD + ignore all 'R' (reserved) bits. The 'P' bit has been reserved for + future usage of end-to-end security. At the time of writing, + there are no end-to-end security mechanisms specified; therefore, + the 'P' bit SHOULD be set to 0. + + The 'M' bit, known as the Mandatory bit, indicates whether the + receiver of the AVP MUST parse and understand the semantics of the + AVP including its content. The receiving entity MUST return an + appropriate error message if it receives an AVP that has the M-bit + + + +Fajardo, et al. Standards Track [Page 41] + +RFC 6733 Diameter Base Protocol October 2012 + + + set but does not understand it. An exception applies when the AVP + is embedded within a Grouped AVP. See Section 4.4 for details. + Diameter relay and redirect agents MUST NOT reject messages with + unrecognized AVPs. + + The 'M' bit MUST be set according to the rules defined in the + application specification that introduces or reuses this AVP. + Within a given application, the M-bit setting for an AVP is + defined either for all command types or for each command type. + + AVPs with the 'M' bit cleared are informational only; a receiver + that receives a message with such an AVP that is not supported, or + whose value is not supported, MAY simply ignore the AVP. + + The 'V' bit, known as the Vendor-Specific bit, indicates whether + the optional Vendor-ID field is present in the AVP header. When + set, the AVP Code belongs to the specific vendor code address + space. + + AVP Length + + The AVP Length field is three octets, and indicates the number of + octets in this AVP including the AVP Code field, AVP Length field, + AVP Flags field, Vendor-ID field (if present), and the AVP Data + field. If a message is received with an invalid attribute length, + the message MUST be rejected. + +4.1.1. Optional Header Elements + + The AVP header contains one optional field. This field is only + present if the respective bit-flag is enabled. + + Vendor-ID + + The Vendor-ID field is present if the 'V' bit is set in the AVP + Flags field. The optional four-octet Vendor-ID field contains the + IANA-assigned "SMI Network Management Private Enterprise Codes" + [ENTERPRISE] value, encoded in network byte order. Any vendors or + standardization organizations that are also treated like vendors + in the IANA-managed "SMI Network Management Private Enterprise + Codes" space wishing to implement a vendor-specific Diameter AVP + MUST use their own Vendor-ID along with their privately managed + AVP address space, guaranteeing that they will not collide with + any other vendor's vendor-specific AVP(s) or with future IETF + AVPs. + + + + + + +Fajardo, et al. Standards Track [Page 42] + +RFC 6733 Diameter Base Protocol October 2012 + + + A Vendor-ID value of zero (0) corresponds to the IETF-adopted AVP + values, as managed by IANA. Since the absence of the Vendor-ID + field implies that the AVP in question is not vendor specific, + implementations MUST NOT use the value of zero (0) for the + Vendor-ID field. + +4.2. Basic AVP Data Formats + + The Data field is zero or more octets and contains information + specific to the Attribute. The format and length of the Data field + is determined by the AVP Code and AVP Length fields. The format of + the Data field MUST be one of the following base data types or a data + type derived from the base data types. In the event that a new Basic + AVP Data Format is needed, a new version of this RFC MUST be created. + + OctetString + + The data contains arbitrary data of variable length. Unless + otherwise noted, the AVP Length field MUST be set to at least 8 + (12 if the 'V' bit is enabled). AVP values of this type that are + not a multiple of 4 octets in length are followed by the necessary + padding so that the next AVP (if any) will start on a 32-bit + boundary. + + Integer32 + + 32-bit signed value, in network byte order. The AVP Length field + MUST be set to 12 (16 if the 'V' bit is enabled). + + Integer64 + + 64-bit signed value, in network byte order. The AVP Length field + MUST be set to 16 (20 if the 'V' bit is enabled). + + Unsigned32 + + 32-bit unsigned value, in network byte order. The AVP Length + field MUST be set to 12 (16 if the 'V' bit is enabled). + + Unsigned64 + + 64-bit unsigned value, in network byte order. The AVP Length + field MUST be set to 16 (20 if the 'V' bit is enabled). + + + + + + + + +Fajardo, et al. Standards Track [Page 43] + +RFC 6733 Diameter Base Protocol October 2012 + + + Float32 + + This represents floating point values of single precision as + described by [FLOATPOINT]. The 32-bit value is transmitted in + network byte order. The AVP Length field MUST be set to 12 (16 if + the 'V' bit is enabled). + + Float64 + + This represents floating point values of double precision as + described by [FLOATPOINT]. The 64-bit value is transmitted in + network byte order. The AVP Length field MUST be set to 16 (20 if + the 'V' bit is enabled). + + Grouped + + The Data field is specified as a sequence of AVPs. These AVPs are + concatenated -- including their headers and padding -- in the + order in which they are specified and the result encapsulated in + the Data field. The AVP Length field is set to 8 (12 if the 'V' + bit is enabled) plus the total length of all included AVPs, + including their headers and padding. Thus, the AVP Length field + of an AVP of type Grouped is always a multiple of 4. + +4.3. Derived AVP Data Formats + + In addition to using the Basic AVP Data Formats, applications may + define data formats derived from the Basic AVP Data Formats. An + application that defines new Derived AVP Data Formats MUST include + them in a section titled "Derived AVP Data Formats", using the same + format as the definitions below. Each new definition MUST be either + defined or listed with a reference to the RFC that defines the + format. + +4.3.1. Common Derived AVP Data Formats + + The following are commonly used Derived AVP Data Formats. + + Address + + The Address format is derived from the OctetString Basic AVP + Format. It is a discriminated union representing, for example, a + 32-bit (IPv4) [RFC0791] or 128-bit (IPv6) [RFC4291] address, most + significant octet first. The first two octets of the Address AVP + represent the AddressType, which contains an Address Family, + defined in [IANAADFAM]. The AddressType is used to discriminate + the content and format of the remaining octets. + + + + +Fajardo, et al. Standards Track [Page 44] + +RFC 6733 Diameter Base Protocol October 2012 + + + Time + + The Time format is derived from the OctetString Basic AVP Format. + The string MUST contain four octets, in the same format as the + first four bytes are in the NTP timestamp format. The NTP + timestamp format is defined in Section 3 of [RFC5905]. + + This represents the number of seconds since 0h on 1 January 1900 + with respect to the Coordinated Universal Time (UTC). + + On 6h 28m 16s UTC, 7 February 2036, the time value will overflow. + Simple Network Time Protocol (SNTP) [RFC5905] describes a + procedure to extend the time to 2104. This procedure MUST be + supported by all Diameter nodes. + + UTF8String + + The UTF8String format is derived from the OctetString Basic AVP + Format. This is a human-readable string represented using the + ISO/IEC IS 10646-1 character set, encoded as an OctetString using + the UTF-8 transformation format [RFC3629]. + + Since additional code points are added by amendments to the 10646 + standard from time to time, implementations MUST be prepared to + encounter any code point from 0x00000001 to 0x7fffffff. Byte + sequences that do not correspond to the valid encoding of a code + point into UTF-8 charset or are outside this range are prohibited. + + The use of control codes SHOULD be avoided. When it is necessary + to represent a new line, the control code sequence CR LF SHOULD be + used. + + The use of leading or trailing white space SHOULD be avoided. + + For code points not directly supported by user interface hardware + or software, an alternative means of entry and display, such as + hexadecimal, MAY be provided. + + For information encoded in 7-bit US-ASCII, the UTF-8 charset is + identical to the US-ASCII charset. + + UTF-8 may require multiple bytes to represent a single character / + code point; thus, the length of a UTF8String in octets may be + different from the number of characters encoded. + + Note that the AVP Length field of an UTF8String is measured in + octets not characters. + + + + +Fajardo, et al. Standards Track [Page 45] + +RFC 6733 Diameter Base Protocol October 2012 + + + DiameterIdentity + + The DiameterIdentity format is derived from the OctetString Basic + AVP Format. + + DiameterIdentity = FQDN/Realm + + The DiameterIdentity value is used to uniquely identify either: + + * A Diameter node for purposes of duplicate connection and + routing loop detection. + + * A Realm to determine whether messages can be satisfied locally + or whether they must be routed or redirected. + + When a DiameterIdentity value is used to identify a Diameter node, + the contents of the string MUST be the Fully Qualified Domain Name + (FQDN) of the Diameter node. If multiple Diameter nodes run on + the same host, each Diameter node MUST be assigned a unique + DiameterIdentity. If a Diameter node can be identified by several + FQDNs, a single FQDN should be picked at startup and used as the + only DiameterIdentity for that node, whatever the connection on + which it is sent. In this document, note that DiameterIdentity is + in ASCII form in order to be compatible with existing DNS + infrastructure. See Appendix D for interactions between the + Diameter protocol and Internationalized Domain Names (IDNs). + + DiameterURI + + The DiameterURI MUST follow the Uniform Resource Identifiers (RFC + 3986) syntax [RFC3986] rules specified below: + + "aaa://" FQDN [ port ] [ transport ] [ protocol ] + + ; No transport security + + "aaas://" FQDN [ port ] [ transport ] [ protocol ] + + ; Transport security used + + FQDN = < Fully Qualified Domain Name > + + + + + + + + + + +Fajardo, et al. Standards Track [Page 46] + +RFC 6733 Diameter Base Protocol October 2012 + + + port = ":" 1*DIGIT + + ; One of the ports used to listen for + ; incoming connections. + ; If absent, the default Diameter port + ; (3868) is assumed if no transport + ; security is used and port 5658 when + ; transport security (TLS/TCP and DTLS/SCTP) + ; is used. + + transport = ";transport=" transport-protocol + + ; One of the transports used to listen + ; for incoming connections. If absent, + ; the default protocol is assumed to be TCP. + ; UDP MUST NOT be used when the aaa-protocol + ; field is set to diameter. + + transport-protocol = ( "tcp" / "sctp" / "udp" ) + + protocol = ";protocol=" aaa-protocol + + ; If absent, the default AAA protocol + ; is Diameter. + + aaa-protocol = ( "diameter" / "radius" / "tacacs+" ) + + The following are examples of valid Diameter host identities: + + aaa://host.example.com;transport=tcp + aaa://host.example.com:6666;transport=tcp + aaa://host.example.com;protocol=diameter + aaa://host.example.com:6666;protocol=diameter + aaa://host.example.com:6666;transport=tcp;protocol=diameter + aaa://host.example.com:1813;transport=udp;protocol=radius + + Enumerated + + The Enumerated format is derived from the Integer32 Basic AVP + Format. The definition contains a list of valid values and their + interpretation and is described in the Diameter application + introducing the AVP. + + + + + + + + + +Fajardo, et al. Standards Track [Page 47] + +RFC 6733 Diameter Base Protocol October 2012 + + + IPFilterRule + + The IPFilterRule format is derived from the OctetString Basic AVP + Format and uses the ASCII charset. The rule syntax is a modified + subset of ipfw(8) from FreeBSD. Packets may be filtered based on + the following information that is associated with it: + + Direction (in or out) + Source and destination IP address (possibly masked) + Protocol + Source and destination port (lists or ranges) + TCP flags + IP fragment flag + IP options + ICMP types + + Rules for the appropriate direction are evaluated in order, with the + first matched rule terminating the evaluation. Each packet is + evaluated once. If no rule matches, the packet is dropped if the + last rule evaluated was a permit, and passed if the last rule was a + deny. + + IPFilterRule filters MUST follow the format: + + action dir proto from src to dst [options] + + action permit - Allow packets that match the rule. + deny - Drop packets that match the rule. + + dir "in" is from the terminal, "out" is to the + terminal. + + proto An IP protocol specified by number. The "ip" + keyword means any protocol will match. + + src and dst <address/mask> [ports] + + The <address/mask> may be specified as: + ipno An IPv4 or IPv6 number in dotted- + quad or canonical IPv6 form. Only + this exact IP number will match the + rule. + + + + + + + + + +Fajardo, et al. Standards Track [Page 48] + +RFC 6733 Diameter Base Protocol October 2012 + + + ipno/bits An IP number as above with a mask + width of the form 192.0.2.10/24. In + this case, all IP numbers from + 192.0.2.0 to 192.0.2.255 will match. + The bit width MUST be valid for the + IP version, and the IP number MUST + NOT have bits set beyond the mask. + For a match to occur, the same IP + version must be present in the + packet that was used in describing + the IP address. To test for a + particular IP version, the bits part + can be set to zero. The keyword + "any" is 0.0.0.0/0 or the IPv6 + equivalent. The keyword "assigned" + is the address or set of addresses + assigned to the terminal. For IPv4, + a typical first rule is often "deny + in ip! assigned". + + The sense of the match can be inverted by + preceding an address with the not modifier (!), + causing all other addresses to be matched + instead. This does not affect the selection of + port numbers. + + With the TCP, UDP, and SCTP protocols, optional + ports may be specified as: + + {port/port-port}[,ports[,...]] + + The '-' notation specifies a range of ports + (including boundaries). + + Fragmented packets that have a non-zero offset + (i.e., not the first fragment) will never match + a rule that has one or more port + specifications. See the frag option for + details on matching fragmented packets. + + options: + frag Match if the packet is a fragment and this is not + the first fragment of the datagram. frag may not + be used in conjunction with either tcpflags or + TCP/UDP port specifications. + + + + + + +Fajardo, et al. Standards Track [Page 49] + +RFC 6733 Diameter Base Protocol October 2012 + + + ipoptions spec + Match if the IP header contains the comma-separated + list of options specified in spec. The + supported IP options are: + + ssrr (strict source route), lsrr (loose source + route), rr (record packet route), and ts + (timestamp). The absence of a particular option + may be denoted with a '!'. + + tcpoptions spec + Match if the TCP header contains the comma-separated + list of options specified in spec. The + supported TCP options are: + + mss (maximum segment size), window (tcp window + advertisement), sack (selective ack), ts (rfc1323 + timestamp), and cc (rfc1644 t/tcp connection + count). The absence of a particular option may + be denoted with a '!'. + + established + TCP packets only. Match packets that have the RST + or ACK bits set. + + setup TCP packets only. Match packets that have the SYN + bit set but no ACK bit. + + + tcpflags spec + TCP packets only. Match if the TCP header + contains the comma-separated list of flags + specified in spec. The supported TCP flags are: + + fin, syn, rst, psh, ack, and urg. The absence of a + particular flag may be denoted with a '!'. A rule + that contains a tcpflags specification can never + match a fragmented packet that has a non-zero + offset. See the frag option for details on + matching fragmented packets. + + icmptypes types + ICMP packets only. Match if the ICMP type is in + the list types. The list may be specified as any + combination of ranges or individual types + separated by commas. Both the numeric values and + the symbolic values listed below can be used. The + supported ICMP types are: + + + +Fajardo, et al. Standards Track [Page 50] + +RFC 6733 Diameter Base Protocol October 2012 + + + echo reply (0), destination unreachable (3), + source quench (4), redirect (5), echo request + (8), router advertisement (9), router + solicitation (10), time-to-live exceeded (11), IP + header bad (12), timestamp request (13), + timestamp reply (14), information request (15), + information reply (16), address mask request (17), + and address mask reply (18). + + There is one kind of packet that the access device MUST always + discard, that is an IP fragment with a fragment offset of one. This + is a valid packet, but it only has one use, to try to circumvent + firewalls. + + An access device that is unable to interpret or apply a deny rule + MUST terminate the session. An access device that is unable to + interpret or apply a permit rule MAY apply a more restrictive rule. + An access device MAY apply deny rules of its own before the supplied + rules, for example to protect the access device owner's + infrastructure. + +4.4. Grouped AVP Values + + The Diameter protocol allows AVP values of type 'Grouped'. This + implies that the Data field is actually a sequence of AVPs. It is + possible to include an AVP with a Grouped type within a Grouped type, + that is, to nest them. AVPs within an AVP of type Grouped have the + same padding requirements as non-Grouped AVPs, as defined in + Section 4.4. + + The AVP Code numbering space of all AVPs included in a Grouped AVP is + the same as for non-Grouped AVPs. Receivers of a Grouped AVP that + does not have the 'M' (mandatory) bit set and one or more of the + encapsulated AVPs within the group has the 'M' (mandatory) bit set + MAY simply be ignored if the Grouped AVP itself is unrecognized. The + rule applies even if the encapsulated AVP with its 'M' (mandatory) + bit set is further encapsulated within other sub-groups, i.e., other + Grouped AVPs embedded within the Grouped AVP. + + Every Grouped AVP definition MUST include a corresponding grammar, + using ABNF [RFC5234] (with modifications), as defined below. + + grouped-avp-def = "<" name ">" "::=" avp + + name-fmt = ALPHA *(ALPHA / DIGIT / "-") + + + + + + +Fajardo, et al. Standards Track [Page 51] + +RFC 6733 Diameter Base Protocol October 2012 + + + name = name-fmt + ; The name has to be the name of an AVP, + ; defined in the base or extended Diameter + ; specifications. + + avp = header *fixed *required *optional + + header = "<" "AVP-Header:" avpcode [vendor] ">" + + avpcode = 1*DIGIT + ; The AVP Code assigned to the Grouped AVP. + + vendor = 1*DIGIT + ; The Vendor-ID assigned to the Grouped AVP. + ; If absent, the default value of zero is + ; used. + +4.4.1. Example AVP with a Grouped Data Type + + The Example-AVP (AVP Code 999999) is of type Grouped and is used to + clarify how Grouped AVP values work. The Grouped Data field has the + following CCF grammar: + + Example-AVP ::= < AVP Header: 999999 > + { Origin-Host } + 1*{ Session-Id } + *[ AVP ] + + An Example-AVP with Grouped Data follows. + + The Origin-Host AVP (Section 6.3) is required. In this case: + + Origin-Host = "example.com". + + One or more Session-Ids must follow. Here there are two: + + Session-Id = + "grump.example.com:33041;23432;893;0AF3B81" + + Session-Id = + "grump.example.com:33054;23561;2358;0AF3B82" + + + + + + + + + + +Fajardo, et al. Standards Track [Page 52] + +RFC 6733 Diameter Base Protocol October 2012 + + + optional AVPs included are + + Recovery-Policy = <binary> + 2163bc1d0ad82371f6bc09484133c3f09ad74a0dd5346d54195a7cf0b35 + 2cabc881839a4fdcfbc1769e2677a4c1fb499284c5f70b48f58503a45c5 + c2d6943f82d5930f2b7c1da640f476f0e9c9572a50db8ea6e51e1c2c7bd + f8bb43dc995144b8dbe297ac739493946803e1cee3e15d9b765008a1b2a + cf4ac777c80041d72c01e691cf751dbf86e85f509f3988e5875dc905119 + 26841f00f0e29a6d1ddc1a842289d440268681e052b30fb638045f7779c + 1d873c784f054f688f5001559ecff64865ef975f3e60d2fd7966b8c7f92 + + Futuristic-Acct-Record = <binary> + fe19da5802acd98b07a5b86cb4d5d03f0314ab9ef1ad0b67111ff3b90a0 + 57fe29620bf3585fd2dd9fcc38ce62f6cc208c6163c008f4258d1bc88b8 + 17694a74ccad3ec69269461b14b2e7a4c111fb239e33714da207983f58c + 41d018d56fe938f3cbf089aac12a912a2f0d1923a9390e5f789cb2e5067 + d3427475e49968f841 + + The data for the optional AVPs is represented in hexadecimal form + since the format of these AVPs is not known at the time of definition + of the Example-AVP group nor (likely) at the time when the example + instance of this AVP is interpreted -- except by Diameter + implementations that support the same set of AVPs. The encoding + example illustrates how padding is used and how length fields are + calculated. Also, note that AVPs may be present in the Grouped AVP + value that the receiver cannot interpret (here, the Recover-Policy + and Futuristic-Acct-Record AVPs). The length of the Example-AVP is + the sum of all the length of the member AVPs, including their + padding, plus the Example-AVP header size. + + + + + + + + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 53] + +RFC 6733 Diameter Base Protocol October 2012 + + + This AVP would be encoded as follows: + + 0 1 2 3 4 5 6 7 + +-------+-------+-------+-------+-------+-------+-------+-------+ + 0 | Example AVP Header (AVP Code = 999999), Length = 496 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 8 | Origin-Host AVP Header (AVP Code = 264), Length = 19 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 16 | 'e' | 'x' | 'a' | 'm' | 'p' | 'l' | 'e' | '.' | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 24 | 'c' | 'o' | 'm' |Padding| Session-Id AVP Header | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 32 | (AVP Code = 263), Length = 49 | 'g' | 'r' | 'u' | 'm' | + +-------+-------+-------+-------+-------+-------+-------+-------+ + . . . + +-------+-------+-------+-------+-------+-------+-------+-------+ + 72 | 'F' | '3' | 'B' | '8' | '1' |Padding|Padding|Padding| + +-------+-------+-------+-------+-------+-------+-------+-------+ + 80 | Session-Id AVP Header (AVP Code = 263), Length = 50 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 88 | 'g' | 'r' | 'u' | 'm' | 'p' | '.' | 'e' | 'x' | + +-------+-------+-------+-------+-------+-------+-------+-------+ + . . . + +-------+-------+-------+-------+-------+-------+-------+-------+ + 120| '5' | '8' | ';' | '0' | 'A' | 'F' | '3' | 'B' | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 128| '8' | '2' |Padding|Padding| Recovery-Policy Header (AVP | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 136| Code = 8341), Length = 223 | 0x21 | 0x63 | 0xbc | 0x1d | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 144| 0x0a | 0xd8 | 0x23 | 0x71 | 0xf6 | 0xbc | 0x09 | 0x48 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + . . . + +-------+-------+-------+-------+-------+-------+-------+-------+ + 352| 0x8c | 0x7f | 0x92 |Padding| Futuristic-Acct-Record Header | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 328|(AVP Code = 15930),Length = 137| 0xfe | 0x19 | 0xda | 0x58 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + 336| 0x02 | 0xac | 0xd9 | 0x8b | 0x07 | 0xa5 | 0xb8 | 0xc6 | + +-------+-------+-------+-------+-------+-------+-------+-------+ + . . . + +-------+-------+-------+-------+-------+-------+-------+-------+ + 488| 0xe4 | 0x99 | 0x68 | 0xf8 | 0x41 |Padding|Padding|Padding| + +-------+-------+-------+-------+-------+-------+-------+-------+ + + + + + + + +Fajardo, et al. Standards Track [Page 54] + +RFC 6733 Diameter Base Protocol October 2012 + + +4.5. Diameter Base Protocol AVPs + + The following table describes the Diameter AVPs defined in the base + protocol, their AVP Code values, types, and possible flag values. + + Due to space constraints, the short form DiamIdent is used to + represent DiameterIdentity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 55] + +RFC 6733 Diameter Base Protocol October 2012 + + + +----------+ + | AVP Flag | + | rules | + |----+-----| + AVP Section | |MUST | + Attribute Name Code Defined Data Type |MUST| NOT | + -----------------------------------------|----+-----| + Acct- 85 9.8.2 Unsigned32 | M | V | + Interim-Interval | | | + Accounting- 483 9.8.7 Enumerated | M | V | + Realtime-Required | | | + Acct- 50 9.8.5 UTF8String | M | V | + Multi-Session-Id | | | + Accounting- 485 9.8.3 Unsigned32 | M | V | + Record-Number | | | + Accounting- 480 9.8.1 Enumerated | M | V | + Record-Type | | | + Acct- 44 9.8.4 OctetString| M | V | + Session-Id | | | + Accounting- 287 9.8.6 Unsigned64 | M | V | + Sub-Session-Id | | | + Acct- 259 6.9 Unsigned32 | M | V | + Application-Id | | | + Auth- 258 6.8 Unsigned32 | M | V | + Application-Id | | | + Auth-Request- 274 8.7 Enumerated | M | V | + Type | | | + Authorization- 291 8.9 Unsigned32 | M | V | + Lifetime | | | + Auth-Grace- 276 8.10 Unsigned32 | M | V | + Period | | | + Auth-Session- 277 8.11 Enumerated | M | V | + State | | | + Re-Auth-Request- 285 8.12 Enumerated | M | V | + Type | | | + Class 25 8.20 OctetString| M | V | + Destination-Host 293 6.5 DiamIdent | M | V | + Destination- 283 6.6 DiamIdent | M | V | + Realm | | | + Disconnect-Cause 273 5.4.3 Enumerated | M | V | + Error-Message 281 7.3 UTF8String | | V,M | + Error-Reporting- 294 7.4 DiamIdent | | V,M | + Host | | | + Event-Timestamp 55 8.21 Time | M | V | + Experimental- 297 7.6 Grouped | M | V | + Result | | | + -----------------------------------------|----+-----| + + + + +Fajardo, et al. Standards Track [Page 56] + +RFC 6733 Diameter Base Protocol October 2012 + + + +----------+ + | AVP Flag | + | rules | + |----+-----| + AVP Section | |MUST | + Attribute Name Code Defined Data Type |MUST| NOT | + -----------------------------------------|----+-----| + Experimental- 298 7.7 Unsigned32 | M | V | + Result-Code | | | + Failed-AVP 279 7.5 Grouped | M | V | + Firmware- 267 5.3.4 Unsigned32 | | V,M | + Revision | | | + Host-IP-Address 257 5.3.5 Address | M | V | + Inband-Security | M | V | + -Id 299 6.10 Unsigned32 | | | + Multi-Round- 272 8.19 Unsigned32 | M | V | + Time-Out | | | + Origin-Host 264 6.3 DiamIdent | M | V | + Origin-Realm 296 6.4 DiamIdent | M | V | + Origin-State-Id 278 8.16 Unsigned32 | M | V | + Product-Name 269 5.3.7 UTF8String | | V,M | + Proxy-Host 280 6.7.3 DiamIdent | M | V | + Proxy-Info 284 6.7.2 Grouped | M | V | + Proxy-State 33 6.7.4 OctetString| M | V | + Redirect-Host 292 6.12 DiamURI | M | V | + Redirect-Host- 261 6.13 Enumerated | M | V | + Usage | | | + Redirect-Max- 262 6.14 Unsigned32 | M | V | + Cache-Time | | | + Result-Code 268 7.1 Unsigned32 | M | V | + Route-Record 282 6.7.1 DiamIdent | M | V | + Session-Id 263 8.8 UTF8String | M | V | + Session-Timeout 27 8.13 Unsigned32 | M | V | + Session-Binding 270 8.17 Unsigned32 | M | V | + Session-Server- 271 8.18 Enumerated | M | V | + Failover | | | + Supported- 265 5.3.6 Unsigned32 | M | V | + Vendor-Id | | | + Termination- 295 8.15 Enumerated | M | V | + Cause | | | + User-Name 1 8.14 UTF8String | M | V | + Vendor-Id 266 5.3.3 Unsigned32 | M | V | + Vendor-Specific- 260 6.11 Grouped | M | V | + Application-Id | | | + -----------------------------------------|----+-----| + + + + + + +Fajardo, et al. Standards Track [Page 57] + +RFC 6733 Diameter Base Protocol October 2012 + + +5. Diameter Peers + + This section describes how Diameter nodes establish connections and + communicate with peers. + +5.1. Peer Connections + + Connections between diameter peers are established using their valid + DiameterIdentity. A Diameter node initiating a connection to a peer + MUST know the peer's DiameterIdentity. Methods for discovering a + Diameter peer can be found in Section 5.2. + + Although a Diameter node may have many possible peers with which it + is able to communicate, it may not be economical to have an + established connection to all of them. At a minimum, a Diameter node + SHOULD have an established connection with two peers per realm, known + as the primary and secondary peers. Of course, a node MAY have + additional connections, if it is deemed necessary. Typically, all + messages for a realm are sent to the primary peer but, in the event + that failover procedures are invoked, any pending requests are sent + to the secondary peer. However, implementations are free to load + balance requests between a set of peers. + + Note that a given peer MAY act as a primary for a given realm while + acting as a secondary for another realm. + + When a peer is deemed suspect, which could occur for various reasons, + including not receiving a DWA within an allotted time frame, no new + requests should be forwarded to the peer, but failover procedures are + invoked. When an active peer is moved to this mode, additional + connections SHOULD be established to ensure that the necessary number + of active connections exists. + + There are two ways that a peer is removed from the suspect peer list: + + 1. The peer is no longer reachable, causing the transport connection + to be shut down. The peer is moved to the closed state. + + 2. Three watchdog messages are exchanged with accepted round-trip + times, and the connection to the peer is considered stabilized. + + In the event the peer being removed is either the primary or + secondary, an alternate peer SHOULD replace the deleted peer and + assume the role of either primary or secondary. + + + + + + + +Fajardo, et al. Standards Track [Page 58] + +RFC 6733 Diameter Base Protocol October 2012 + + +5.2. Diameter Peer Discovery + + Allowing for dynamic Diameter agent discovery makes possible simpler + and more robust deployment of Diameter services. In order to promote + interoperable implementations of Diameter peer discovery, the + following mechanisms (manual configuration and DNS) are described. + These are based on existing IETF standards. Both mechanisms MUST be + supported by all Diameter implementations; either MAY be used. + + There are two cases where Diameter peer discovery may be performed. + The first is when a Diameter client needs to discover a first-hop + Diameter agent. The second case is when a Diameter agent needs to + discover another agent for further handling of a Diameter operation. + In both cases, the following 'search order' is recommended: + + 1. The Diameter implementation consults its list of statically + (manually) configured Diameter agent locations. These will be + used if they exist and respond. + + 2. The Diameter implementation performs a NAPTR query for a server + in a particular realm. The Diameter implementation has to know, + in advance, in which realm to look for a Diameter agent. This + could be deduced, for example, from the 'realm' in an NAI on + which a Diameter implementation needed to perform a Diameter + operation. + + The NAPTR usage in Diameter follows the S-NAPTR DDDS application + [RFC3958] in which the SERVICE field includes tags for the + desired application and supported application protocol. The + application service tag for a Diameter application is 'aaa' and + the supported application protocol tags are 'diameter.tcp', + 'diameter.sctp', 'diameter.dtls', or 'diameter.tls.tcp' + [RFC6408]. + + The client can follow the resolution process defined by the + S-NAPTR DDDS [RFC3958] application to find a matching SRV, A, or + AAAA record of a suitable peer. The domain suffixes in the NAPTR + replacement field SHOULD match the domain of the original query. + An example can be found in Appendix B. + + 3. If no NAPTR records are found, the requester directly queries for + one of the following SRV records: for Diameter over TCP, use + "_diameter._tcp.realm"; for Diameter over TLS, use + "_diameters._tcp.realm"; for Diameter over SCTP, use + "_diameter._sctp.realm"; for Diameter over DTLS, use + "_diameters._sctp.realm". If SRV records are found, then the + requester can perform address record query (A RR's and/or AAAA + + + + +Fajardo, et al. Standards Track [Page 59] + +RFC 6733 Diameter Base Protocol October 2012 + + + RR's) for the target hostname specified in the SRV records + following the rules given in [RFC2782]. If no SRV records are + found, the requester gives up. + + If the server is using a site certificate, the domain name in the + NAPTR query and the domain name in the replacement field MUST both be + valid based on the site certificate handed out by the server in the + TLS/TCP and DTLS/SCTP or Internet Key Exchange Protocol (IKE) + exchange. Similarly, the domain name in the SRV query and the domain + name in the target in the SRV record MUST both be valid based on the + same site certificate. Otherwise, an attacker could modify the DNS + records to contain replacement values in a different domain, and the + client could not validate whether this was the desired behavior or + the result of an attack. + + Also, the Diameter peer MUST check to make sure that the discovered + peers are authorized to act in its role. Authentication via IKE or + TLS/TCP and DTLS/SCTP, or validation of DNS RRs via DNSSEC is not + sufficient to conclude this. For example, a web server may have + obtained a valid TLS/TCP and DTLS/SCTP certificate, and secured RRs + may be included in the DNS, but this does not imply that it is + authorized to act as a Diameter server. + + Authorization can be achieved, for example, by the configuration of a + Diameter server Certification Authority (CA). The server CA issues a + certificate to the Diameter server, which includes an Object + Identifier (OID) to indicate the subject is a Diameter server in the + Extended Key Usage extension [RFC5280]. This certificate is then + used during TLS/TCP, DTLS/SCTP, or IKE security negotiation. + However, note that, at the time of writing, no Diameter server + Certification Authorities exist. + + A dynamically discovered peer causes an entry in the peer table (see + Section 2.6) to be created. Note that entries created via DNS MUST + expire (or be refreshed) within the DNS Time to Live (TTL). If a + peer is discovered outside of the local realm, a routing table entry + (see Section 2.7) for the peer's realm is created. The routing table + entry's expiration MUST match the peer's expiration value. + +5.3. Capabilities Exchange + + When two Diameter peers establish a transport connection, they MUST + exchange the Capabilities Exchange messages, as specified in the peer + state machine (see Section 5.6). This message allows the discovery + of a peer's identity and its capabilities (protocol version number, + the identifiers of supported Diameter applications, security + mechanisms, etc.). + + + + +Fajardo, et al. Standards Track [Page 60] + +RFC 6733 Diameter Base Protocol October 2012 + + + The receiver only issues commands to its peers that have advertised + support for the Diameter application that defines the command. A + Diameter node MUST cache the supported Application Ids in order to + ensure that unrecognized commands and/or AVPs are not unnecessarily + sent to a peer. + + A receiver of a Capabilities-Exchange-Request (CER) message that does + not have any applications in common with the sender MUST return a + Capabilities-Exchange-Answer (CEA) with the Result-Code AVP set to + DIAMETER_NO_COMMON_APPLICATION and SHOULD disconnect the transport + layer connection. Note that receiving a CER or CEA from a peer + advertising itself as a relay (see Section 2.4) MUST be interpreted + as having common applications with the peer. + + The receiver of the Capabilities-Exchange-Request (CER) MUST + determine common applications by computing the intersection of its + own set of supported Application Ids against all of the + Application-Id AVPs (Auth-Application-Id, Acct-Application-Id, and + Vendor-Specific-Application-Id) present in the CER. The value of the + Vendor-Id AVP in the Vendor-Specific-Application-Id MUST NOT be used + during computation. The sender of the Capabilities-Exchange-Answer + (CEA) SHOULD include all of its supported applications as a hint to + the receiver regarding all of its application capabilities. + + Diameter implementations SHOULD first attempt to establish a TLS/TCP + and DTLS/SCTP connection prior to the CER/CEA exchange. This + protects the capabilities information of both peers. To support + older Diameter implementations that do not fully conform to this + document, the transport security MAY still be negotiated via an + Inband-Security AVP. In this case, the receiver of a Capabilities- + Exchange-Request (CER) message that does not have any security + mechanisms in common with the sender MUST return a Capabilities- + Exchange-Answer (CEA) with the Result-Code AVP set to + DIAMETER_NO_COMMON_SECURITY and SHOULD disconnect the transport layer + connection. + + CERs received from unknown peers MAY be silently discarded, or a CEA + MAY be issued with the Result-Code AVP set to DIAMETER_UNKNOWN_PEER. + In both cases, the transport connection is closed. If the local + policy permits receiving CERs from unknown hosts, a successful CEA + MAY be returned. If a CER from an unknown peer is answered with a + successful CEA, the lifetime of the peer entry is equal to the + lifetime of the transport connection. In case of a transport + failure, all the pending transactions destined to the unknown peer + can be discarded. + + The CER and CEA messages MUST NOT be proxied, redirected, or relayed. + + + + +Fajardo, et al. Standards Track [Page 61] + +RFC 6733 Diameter Base Protocol October 2012 + + + Since the CER/CEA messages cannot be proxied, it is still possible + that an upstream agent will receive a message for which it has no + available peers to handle the application that corresponds to the + Command Code. In such instances, the 'E' bit is set in the answer + message (Section 7) with the Result-Code AVP set to + DIAMETER_UNABLE_TO_DELIVER to inform the downstream agent to take + action (e.g., re-routing request to an alternate peer). + + With the exception of the Capabilities-Exchange-Request message, a + message of type Request that includes the Auth-Application-Id or + Acct-Application-Id AVPs, or a message with an application-specific + Command Code MAY only be forwarded to a host that has explicitly + advertised support for the application (or has advertised the Relay + Application Id). + +5.3.1. Capabilities-Exchange-Request + + The Capabilities-Exchange-Request (CER), indicated by the Command + Code set to 257 and the Command Flags' 'R' bit set, is sent to + exchange local capabilities. Upon detection of a transport failure, + this message MUST NOT be sent to an alternate peer. + + When Diameter is run over SCTP [RFC4960] or DTLS/SCTP [RFC6083], + which allow for connections to span multiple interfaces and multiple + IP addresses, the Capabilities-Exchange-Request message MUST contain + one Host-IP-Address AVP for each potential IP address that MAY be + locally used when transmitting Diameter messages. + + Message Format + + <CER> ::= < Diameter Header: 257, REQ > + { Origin-Host } + { Origin-Realm } + 1* { Host-IP-Address } + { Vendor-Id } + { Product-Name } + [ Origin-State-Id ] + * [ Supported-Vendor-Id ] + * [ Auth-Application-Id ] + * [ Inband-Security-Id ] + * [ Acct-Application-Id ] + * [ Vendor-Specific-Application-Id ] + [ Firmware-Revision ] + * [ AVP ] + + + + + + + +Fajardo, et al. Standards Track [Page 62] + +RFC 6733 Diameter Base Protocol October 2012 + + +5.3.2. Capabilities-Exchange-Answer + + The Capabilities-Exchange-Answer (CEA), indicated by the Command Code + set to 257 and the Command Flags' 'R' bit cleared, is sent in + response to a CER message. + + When Diameter is run over SCTP [RFC4960] or DTLS/SCTP [RFC6083], + which allow connections to span multiple interfaces, hence, multiple + IP addresses, the Capabilities-Exchange-Answer message MUST contain + one Host-IP-Address AVP for each potential IP address that MAY be + locally used when transmitting Diameter messages. + + Message Format + + <CEA> ::= < Diameter Header: 257 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + 1* { Host-IP-Address } + { Vendor-Id } + { Product-Name } + [ Origin-State-Id ] + [ Error-Message ] + [ Failed-AVP ] + * [ Supported-Vendor-Id ] + * [ Auth-Application-Id ] + * [ Inband-Security-Id ] + * [ Acct-Application-Id ] + * [ Vendor-Specific-Application-Id ] + [ Firmware-Revision ] + * [ AVP ] + +5.3.3. Vendor-Id AVP + + The Vendor-Id AVP (AVP Code 266) is of type Unsigned32 and contains + the IANA "SMI Network Management Private Enterprise Codes" + [ENTERPRISE] value assigned to the Diameter Software vendor. It is + envisioned that the combination of the Vendor-Id, Product-Name + (Section 5.3.7), and Firmware-Revision (Section 5.3.4) AVPs may + provide useful debugging information. + + A Vendor-Id value of zero in the CER or CEA message is reserved and + indicates that this field is ignored. + + + + + + + + +Fajardo, et al. Standards Track [Page 63] + +RFC 6733 Diameter Base Protocol October 2012 + + +5.3.4. Firmware-Revision AVP + + The Firmware-Revision AVP (AVP Code 267) is of type Unsigned32 and is + used to inform a Diameter peer of the firmware revision of the + issuing device. + + For devices that do not have a firmware revision (general-purpose + computers running Diameter software modules, for instance), the + revision of the Diameter software module may be reported instead. + +5.3.5. Host-IP-Address AVP + + The Host-IP-Address AVP (AVP Code 257) is of type Address and is used + to inform a Diameter peer of the sender's IP address. All source + addresses that a Diameter node expects to use with SCTP [RFC4960] or + DTLS/SCTP [RFC6083] MUST be advertised in the CER and CEA messages by + including a Host-IP-Address AVP for each address. + +5.3.6. Supported-Vendor-Id AVP + + The Supported-Vendor-Id AVP (AVP Code 265) is of type Unsigned32 and + contains the IANA "SMI Network Management Private Enterprise Codes" + [ENTERPRISE] value assigned to a vendor other than the device vendor + but including the application vendor. This is used in the CER and + CEA messages in order to inform the peer that the sender supports (a + subset of) the Vendor-Specific AVPs defined by the vendor identified + in this AVP. The value of this AVP MUST NOT be set to zero. + Multiple instances of this AVP containing the same value SHOULD NOT + be sent. + +5.3.7. Product-Name AVP + + The Product-Name AVP (AVP Code 269) is of type UTF8String and + contains the vendor-assigned name for the product. The Product-Name + AVP SHOULD remain constant across firmware revisions for the same + product. + +5.4. Disconnecting Peer Connections + + When a Diameter node disconnects one of its transport connections, + its peer cannot know the reason for the disconnect and will most + likely assume that a connectivity problem occurred or that the peer + has rebooted. In these cases, the peer may periodically attempt to + reconnect, as stated in Section 2.1. In the event that the + disconnect was a result of either a shortage of internal resources or + simply that the node in question has no intentions of forwarding any + Diameter messages to the peer in the foreseeable future, a periodic + + + + +Fajardo, et al. Standards Track [Page 64] + +RFC 6733 Diameter Base Protocol October 2012 + + + connection request would not be welcomed. The Disconnection-Reason + AVP contains the reason the Diameter node issued the Disconnect-Peer- + Request message. + + The Disconnect-Peer-Request message is used by a Diameter node to + inform its peer of its intent to disconnect the transport layer and + that the peer shouldn't reconnect unless it has a valid reason to do + so (e.g., message to be forwarded). Upon receipt of the message, the + Disconnect-Peer-Answer message is returned, which SHOULD contain an + error if messages have recently been forwarded, and are likely in + flight, which would otherwise cause a race condition. + + The receiver of the Disconnect-Peer-Answer message initiates the + transport disconnect. The sender of the Disconnect-Peer-Answer + message should be able to detect the transport closure and clean up + the connection. + +5.4.1. Disconnect-Peer-Request + + The Disconnect-Peer-Request (DPR), indicated by the Command Code set + to 282 and the Command Flags' 'R' bit set, is sent to a peer to + inform it of its intentions to shut down the transport connection. + Upon detection of a transport failure, this message MUST NOT be sent + to an alternate peer. + + Message Format + + <DPR> ::= < Diameter Header: 282, REQ > + { Origin-Host } + { Origin-Realm } + { Disconnect-Cause } + * [ AVP ] + +5.4.2. Disconnect-Peer-Answer + + The Disconnect-Peer-Answer (DPA), indicated by the Command Code set + to 282 and the Command Flags' 'R' bit cleared, is sent as a response + to the Disconnect-Peer-Request message. Upon receipt of this + message, the transport connection is shut down. + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 65] + +RFC 6733 Diameter Base Protocol October 2012 + + + Message Format + + <DPA> ::= < Diameter Header: 282 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Error-Message ] + [ Failed-AVP ] + * [ AVP ] + + +5.4.3. Disconnect-Cause AVP + + The Disconnect-Cause AVP (AVP Code 273) is of type Enumerated. A + Diameter node MUST include this AVP in the Disconnect-Peer-Request + message to inform the peer of the reason for its intention to shut + down the transport connection. The following values are supported: + + REBOOTING 0 + A scheduled reboot is imminent. A receiver of a DPR with + above result code MAY attempt reconnection. + + BUSY 1 + The peer's internal resources are constrained, and it has + determined that the transport connection needs to be closed. + A receiver of a DPR with above result code SHOULD NOT attempt + reconnection. + + DO_NOT_WANT_TO_TALK_TO_YOU 2 + The peer has determined that it does not see a need for the + transport connection to exist, since it does not expect any + messages to be exchanged in the near future. A receiver of a + DPR with above result code SHOULD NOT attempt reconnection. + +5.5. Transport Failure Detection + + Given the nature of the Diameter protocol, it is recommended that + transport failures be detected as soon as possible. Detecting such + failures will minimize the occurrence of messages sent to unavailable + agents, resulting in unnecessary delays, and will provide better + failover performance. The Device-Watchdog-Request and Device- + Watchdog-Answer messages, defined in this section, are used to pro- + actively detect transport failures. + + + + + + + + +Fajardo, et al. Standards Track [Page 66] + +RFC 6733 Diameter Base Protocol October 2012 + + +5.5.1. Device-Watchdog-Request + + The Device-Watchdog-Request (DWR), indicated by the Command Code set + to 280 and the Command Flags' 'R' bit set, is sent to a peer when no + traffic has been exchanged between two peers (see Section 5.5.3). + Upon detection of a transport failure, this message MUST NOT be sent + to an alternate peer. + + Message Format + + <DWR> ::= < Diameter Header: 280, REQ > + { Origin-Host } + { Origin-Realm } + [ Origin-State-Id ] + * [ AVP ] + +5.5.2. Device-Watchdog-Answer + + The Device-Watchdog-Answer (DWA), indicated by the Command Code set + to 280 and the Command Flags' 'R' bit cleared, is sent as a response + to the Device-Watchdog-Request message. + + Message Format + + <DWA> ::= < Diameter Header: 280 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Error-Message ] + [ Failed-AVP ] + [ Origin-State-Id ] + * [ AVP ] + +5.5.3. Transport Failure Algorithm + + The transport failure algorithm is defined in [RFC3539]. All + Diameter implementations MUST support the algorithm defined in that + specification in order to be compliant to the Diameter base protocol. + +5.5.4. Failover and Failback Procedures + + In the event that a transport failure is detected with a peer, it is + necessary for all pending request messages to be forwarded to an + alternate agent, if possible. This is commonly referred to as + "failover". + + + + + + +Fajardo, et al. Standards Track [Page 67] + +RFC 6733 Diameter Base Protocol October 2012 + + + In order for a Diameter node to perform failover procedures, it is + necessary for the node to maintain a pending message queue for a + given peer. When an answer message is received, the corresponding + request is removed from the queue. The Hop-by-Hop Identifier field + is used to match the answer with the queued request. + + When a transport failure is detected, if possible, all messages in + the queue are sent to an alternate agent with the T flag set. On + booting a Diameter client or agent, the T flag is also set on any + remaining records in non-volatile storage that are still waiting to + be transmitted. An example of a case where it is not possible to + forward the message to an alternate server is when the message has a + fixed destination, and the unavailable peer is the message's final + destination (see Destination-Host AVP). Such an error requires that + the agent return an answer message with the 'E' bit set and the + Result-Code AVP set to DIAMETER_UNABLE_TO_DELIVER. + + It is important to note that multiple identical requests or answers + MAY be received as a result of a failover. The End-to-End Identifier + field in the Diameter header along with the Origin-Host AVP MUST be + used to identify duplicate messages. + + As described in Section 2.1, a connection request should be + periodically attempted with the failed peer in order to re-establish + the transport connection. Once a connection has been successfully + established, messages can once again be forwarded to the peer. This + is commonly referred to as "failback". + +5.6. Peer State Machine + + This section contains a finite state machine that MUST be observed by + all Diameter implementations. Each Diameter node MUST follow the + state machine described below when communicating with each peer. + Multiple actions are separated by commas, and may continue on + succeeding lines, as space requires. Similarly, state and next state + may also span multiple lines, as space requires. + + This state machine is closely coupled with the state machine + described in [RFC3539], which is used to open, close, failover, + probe, and reopen transport connections. In particular, note that + [RFC3539] requires the use of watchdog messages to probe connections. + For Diameter, DWR and DWA messages are to be used. + + The I- prefix is used to represent the initiator (connecting) + connection, while the R- prefix is used to represent the responder + (listening) connection. The lack of a prefix indicates that the + event or action is the same regardless of the connection on which the + event occurred. + + + +Fajardo, et al. Standards Track [Page 68] + +RFC 6733 Diameter Base Protocol October 2012 + + + The stable states that a state machine may be in are Closed, I-Open, + and R-Open; all other states are intermediate. Note that I-Open and + R-Open are equivalent except for whether the initiator or responder + transport connection is used for communication. + + A CER message is always sent on the initiating connection immediately + after the connection request is successfully completed. In the case + of an election, one of the two connections will shut down. The + responder connection will survive if the Origin-Host of the local + Diameter entity is higher than that of the peer; the initiator + connection will survive if the peer's Origin-Host is higher. All + subsequent messages are sent on the surviving connection. Note that + the results of an election on one peer are guaranteed to be the + inverse of the results on the other. + + For TLS/TCP and DTLS/SCTP usage, a TLS/TCP and DTLS/SCTP handshake + SHOULD begin when both ends are in the closed state prior to any + Diameter message exchanges. The TLS/TCP and DTLS/SCTP connection + SHOULD be established before sending any CER or CEA message to secure + and protect the capabilities information of both peers. The TLS/TCP + and DTLS/SCTP connection SHOULD be disconnected when the state + machine moves to the closed state. When connecting to responders + that do not conform to this document (i.e., older Diameter + implementations that are not prepared to received TLS/TCP and DTLS/ + SCTP connections in the closed state), the initial TLS/TCP and DTLS/ + SCTP connection attempt will fail. The initiator MAY then attempt to + connect via TCP or SCTP and initiate the TLS/TCP and DTLS/SCTP + handshake when both ends are in the open state. If the handshake is + successful, all further messages will be sent via TLS/TCP and DTLS/ + SCTP. If the handshake fails, both ends move to the closed state. + + The state machine constrains only the behavior of a Diameter + implementation as seen by Diameter peers through events on the wire. + + Any implementation that produces equivalent results is considered + compliant. + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 69] + +RFC 6733 Diameter Base Protocol October 2012 + + + state event action next state + ----------------------------------------------------------------- + Closed Start I-Snd-Conn-Req Wait-Conn-Ack + R-Conn-CER R-Accept, R-Open + Process-CER, + R-Snd-CEA + + Wait-Conn-Ack I-Rcv-Conn-Ack I-Snd-CER Wait-I-CEA + I-Rcv-Conn-Nack Cleanup Closed + R-Conn-CER R-Accept, Wait-Conn-Ack/ + Process-CER Elect + Timeout Error Closed + + Wait-I-CEA I-Rcv-CEA Process-CEA I-Open + R-Conn-CER R-Accept, Wait-Returns + Process-CER, + Elect + I-Peer-Disc I-Disc Closed + I-Rcv-Non-CEA Error Closed + Timeout Error Closed + + Wait-Conn-Ack/ I-Rcv-Conn-Ack I-Snd-CER,Elect Wait-Returns + Elect I-Rcv-Conn-Nack R-Snd-CEA R-Open + R-Peer-Disc R-Disc Wait-Conn-Ack + R-Conn-CER R-Reject Wait-Conn-Ack/ + Elect + Timeout Error Closed + + Wait-Returns Win-Election I-Disc,R-Snd-CEA R-Open + I-Peer-Disc I-Disc, R-Open + R-Snd-CEA + I-Rcv-CEA R-Disc I-Open + R-Peer-Disc R-Disc Wait-I-CEA + R-Conn-CER R-Reject Wait-Returns + Timeout Error Closed + + R-Open Send-Message R-Snd-Message R-Open + R-Rcv-Message Process R-Open + R-Rcv-DWR Process-DWR, R-Open + R-Snd-DWA + R-Rcv-DWA Process-DWA R-Open + R-Conn-CER R-Reject R-Open + Stop R-Snd-DPR Closing + R-Rcv-DPR R-Snd-DPA Closing + R-Peer-Disc R-Disc Closed + + + + + + +Fajardo, et al. Standards Track [Page 70] + +RFC 6733 Diameter Base Protocol October 2012 + + + I-Open Send-Message I-Snd-Message I-Open + I-Rcv-Message Process I-Open + I-Rcv-DWR Process-DWR, I-Open + I-Snd-DWA + I-Rcv-DWA Process-DWA I-Open + R-Conn-CER R-Reject I-Open + Stop I-Snd-DPR Closing + I-Rcv-DPR I-Snd-DPA Closing + I-Peer-Disc I-Disc Closed + + Closing I-Rcv-DPA I-Disc Closed + R-Rcv-DPA R-Disc Closed + Timeout Error Closed + I-Peer-Disc I-Disc Closed + R-Peer-Disc R-Disc Closed + +5.6.1. Incoming Connections + + When a connection request is received from a Diameter peer, it is + not, in the general case, possible to know the identity of that peer + until a CER is received from it. This is because host and port + determine the identity of a Diameter peer; the source port of an + incoming connection is arbitrary. Upon receipt of a CER, the + identity of the connecting peer can be uniquely determined from the + Origin-Host. + + For this reason, a Diameter peer must employ logic separate from the + state machine to receive connection requests, accept them, and await + the CER. Once the CER arrives on a new connection, the Origin-Host + that identifies the peer is used to locate the state machine + associated with that peer, and the new connection and CER are passed + to the state machine as an R-Conn-CER event. + + The logic that handles incoming connections SHOULD close and discard + the connection if any message other than a CER arrives or if an + implementation-defined timeout occurs prior to receipt of CER. + + Because handling of incoming connections up to and including receipt + of a CER requires logic, separate from that of any individual state + machine associated with a particular peer, it is described separately + in this section rather than in the state machine above. + +5.6.2. Events + + Transitions and actions in the automaton are caused by events. In + this section, we will ignore the I- and R- prefixes, since the actual + event would be identical, but it would occur on one of two possible + connections. + + + +Fajardo, et al. Standards Track [Page 71] + +RFC 6733 Diameter Base Protocol October 2012 + + + Start The Diameter application has signaled that a + connection should be initiated with the peer. + + R-Conn-CER An acknowledgement is received stating that the + transport connection has been established, and the + associated CER has arrived. + + Rcv-Conn-Ack A positive acknowledgement is received confirming that + the transport connection is established. + + Rcv-Conn-Nack A negative acknowledgement was received stating that + the transport connection was not established. + + Timeout An application-defined timer has expired while waiting + for some event. + + Rcv-CER A CER message from the peer was received. + + Rcv-CEA A CEA message from the peer was received. + + Rcv-Non-CEA A message, other than a CEA, from the peer was + received. + + Peer-Disc A disconnection indication from the peer was received. + + Rcv-DPR A DPR message from the peer was received. + + Rcv-DPA A DPA message from the peer was received. + + Win-Election An election was held, and the local node was the + winner. + + Send-Message A message is to be sent. + + Rcv-Message A message other than CER, CEA, DPR, DPA, DWR, or DWA + was received. + + Stop The Diameter application has signaled that a + connection should be terminated (e.g., on system + shutdown). + +5.6.3. Actions + + Actions in the automaton are caused by events and typically indicate + the transmission of packets and/or an action to be taken on the + connection. In this section, we will ignore the I- and R- prefixes, + since the actual action would be identical, but it would occur on one + of two possible connections. + + + +Fajardo, et al. Standards Track [Page 72] + +RFC 6733 Diameter Base Protocol October 2012 + + + Snd-Conn-Req A transport connection is initiated with the peer. + + Accept The incoming connection associated with the R-Conn-CER + is accepted as the responder connection. + + Reject The incoming connection associated with the R-Conn-CER + is disconnected. + + Process-CER The CER associated with the R-Conn-CER is processed. + + Snd-CER A CER message is sent to the peer. + + Snd-CEA A CEA message is sent to the peer. + + Cleanup If necessary, the connection is shut down, and any + local resources are freed. + + Error The transport layer connection is disconnected, + either politely or abortively, in response to + an error condition. Local resources are freed. + + Process-CEA A received CEA is processed. + + Snd-DPR A DPR message is sent to the peer. + + Snd-DPA A DPA message is sent to the peer. + + Disc The transport layer connection is disconnected, + and local resources are freed. + + Elect An election occurs (see Section 5.6.4 for more + information). + + Snd-Message A message is sent. + + Snd-DWR A DWR message is sent. + + Snd-DWA A DWA message is sent. + + Process-DWR The DWR message is serviced. + + Process-DWA The DWA message is serviced. + + Process A message is serviced. + + + + + + + +Fajardo, et al. Standards Track [Page 73] + +RFC 6733 Diameter Base Protocol October 2012 + + +5.6.4. The Election Process + + The election is performed on the responder. The responder compares + the Origin-Host received in the CER with its own Origin-Host as two + streams of octets. If the local Origin-Host lexicographically + succeeds the received Origin-Host, a Win-Election event is issued + locally. Diameter identities are in ASCII form; therefore, the + lexical comparison is consistent with DNS case insensitivity, where + octets that fall in the ASCII range 'a' through 'z' MUST compare + equally to their uppercase counterparts between 'A' and 'Z'. See + Appendix D for interactions between the Diameter protocol and + Internationalized Domain Name (IDNs). + + The winner of the election MUST close the connection it initiated. + Historically, maintaining the responder side of a connection was more + efficient than maintaining the initiator side. However, current + practices makes this distinction irrelevant. + +6. Diameter Message Processing + + This section describes how Diameter requests and answers are created + and processed. + +6.1. Diameter Request Routing Overview + + A request is sent towards its final destination using one of the + following three combinations of the Destination-Realm and + Destination-Host AVPs: + + o A request that is not able to be proxied (such as a CER) MUST NOT + contain either Destination-Realm or Destination-Host AVPs. + + o A request that needs to be sent to a home server serving a + specific realm, but not to a specific server (such as the first + request of a series of round trips), MUST contain a Destination- + Realm AVP but MUST NOT contain a Destination-Host AVP. For + Diameter clients, the value of the Destination-Realm AVP MAY be + extracted from the User-Name AVP, or other methods. + + o Otherwise, a request that needs to be sent to a specific home + server among those serving a given realm MUST contain both the + Destination-Realm and Destination-Host AVPs. + + The Destination-Host AVP is used as described above when the + destination of the request is fixed, which includes: + + o Authentication requests that span multiple round trips. + + + + +Fajardo, et al. Standards Track [Page 74] + +RFC 6733 Diameter Base Protocol October 2012 + + + o A Diameter message that uses a security mechanism that makes use + of a pre-established session key shared between the source and the + final destination of the message. + + o Server-initiated messages that MUST be received by a specific + Diameter client (e.g., access device), such as the Abort-Session- + Request message, which is used to request that a particular user's + session be terminated. + + Note that an agent can only forward a request to a host described in + the Destination-Host AVP if the host in question is included in its + peer table (see Section 2.6). Otherwise, the request is routed based + on the Destination-Realm only (see Section 6.1.6). + + When a message is received, the message is processed in the following + order: + + o If the message is destined for the local host, the procedures + listed in Section 6.1.4 are followed. + + o If the message is intended for a Diameter peer with whom the local + host is able to directly communicate, the procedures listed in + Section 6.1.5 are followed. This is known as "Request + Forwarding". + + o The procedure listed in Section 6.1.6 is followed, which is known + as "Request Routing". + + o If none of the above are successful, an answer is returned with + the Result-Code set to DIAMETER_UNABLE_TO_DELIVER, with the 'E' + bit set. + + For routing of Diameter messages to work within an administrative + domain, all Diameter nodes within the realm MUST be peers. + + The overview contained in this section (6.1) is intended to provide + general guidelines to Diameter developers. Implementations are free + to use different methods than the ones described here as long as they + conform to the requirements specified in Sections 6.1.1 through + 6.1.9. See Section 7 for more details on error handling. + +6.1.1. Originating a Request + + When creating a request, in addition to any other procedures + described in the application definition for that specific request, + the following procedures MUST be followed: + + + + + +Fajardo, et al. Standards Track [Page 75] + +RFC 6733 Diameter Base Protocol October 2012 + + + o the Command Code is set to the appropriate value; + + o the 'R' bit is set; + + o the End-to-End Identifier is set to a locally unique value; + + o the Origin-Host and Origin-Realm AVPs MUST be set to the + appropriate values, used to identify the source of the message; + and + + o the Destination-Host and Destination-Realm AVPs MUST be set to the + appropriate values, as described in Section 6.1. + +6.1.2. Sending a Request + + When sending a request, originated either locally or as the result of + a forwarding or routing operation, the following procedures SHOULD be + followed: + + o The Hop-by-Hop Identifier SHOULD be set to a locally unique value. + + o The message SHOULD be saved in the list of pending requests. + + Other actions to perform on the message based on the particular role + the agent is playing are described in the following sections. + +6.1.3. Receiving Requests + + A relay or proxy agent MUST check for forwarding loops when receiving + requests. A loop is detected if the server finds its own identity in + a Route-Record AVP. When such an event occurs, the agent MUST answer + with the Result-Code AVP set to DIAMETER_LOOP_DETECTED. + +6.1.4. Processing Local Requests + + A request is known to be for local consumption when one of the + following conditions occurs: + + o The Destination-Host AVP contains the local host's identity; + + o The Destination-Host AVP is not present, the Destination-Realm AVP + contains a realm the server is configured to process locally, and + the Diameter application is locally supported; or + + o Both the Destination-Host and the Destination-Realm are not + present. + + + + + +Fajardo, et al. Standards Track [Page 76] + +RFC 6733 Diameter Base Protocol October 2012 + + + When a request is locally processed, the rules in Section 6.2 should + be used to generate the corresponding answer. + +6.1.5. Request Forwarding + + Request forwarding is done using the Diameter peer table. The + Diameter peer table contains all of the peers with which the local + node is able to directly communicate. + + When a request is received, and the host encoded in the Destination- + Host AVP is one that is present in the peer table, the message SHOULD + be forwarded to the peer. + +6.1.6. Request Routing + + Diameter request message routing is done via realms and Application + Ids. A Diameter message that may be forwarded by Diameter agents + (proxies, redirect agents, or relay agents) MUST include the target + realm in the Destination-Realm AVP. Request routing SHOULD rely on + the Destination-Realm AVP and the Application Id present in the + request message header to aid in the routing decision. The realm MAY + be retrieved from the User-Name AVP, which is in the form of a + Network Access Identifier (NAI). The realm portion of the NAI is + inserted in the Destination-Realm AVP. + + Diameter agents MAY have a list of locally supported realms and + applications, and they MAY have a list of externally supported realms + and applications. When a request is received that includes a realm + and/or application that is not locally supported, the message is + routed to the peer configured in the routing table (see Section 2.7). + + Realm names and Application Ids are the minimum supported routing + criteria, additional information may be needed to support redirect + semantics. + +6.1.7. Predictive Loop Avoidance + + Before forwarding or routing a request, Diameter agents, in addition + to performing the processing described in Section 6.1.3, SHOULD check + for the presence of a candidate route's peer identity in any of the + Route-Record AVPs. In the event of the agent detecting the presence + of a candidate route's peer identity in a Route-Record AVP, the agent + MUST ignore such a route for the Diameter request message and attempt + alternate routes if any exist. In case all the candidate routes are + eliminated by the above criteria, the agent SHOULD return a + DIAMETER_UNABLE_TO_DELIVER message. + + + + + +Fajardo, et al. Standards Track [Page 77] + +RFC 6733 Diameter Base Protocol October 2012 + + +6.1.8. Redirecting Requests + + When a redirect agent receives a request whose routing entry is set + to REDIRECT, it MUST reply with an answer message with the 'E' bit + set, while maintaining the Hop-by-Hop Identifier in the header, and + include the Result-Code AVP to DIAMETER_REDIRECT_INDICATION. Each of + the servers associated with the routing entry are added in a separate + Redirect-Host AVP. + + +------------------+ + | Diameter | + | Redirect Agent | + +------------------+ + ^ | 2. command + 'E' bit + 1. Request | | Result-Code = + [email protected] | | DIAMETER_REDIRECT_INDICATION + + | | Redirect-Host AVP(s) + | v + +-------------+ 3. Request +-------------+ + | example.com |------------->| example.net | + | Relay | | Diameter | + | Agent |<-------------| Server | + +-------------+ 4. Answer +-------------+ + + Figure 5: Diameter Redirect Agent + + The receiver of an answer message with the 'E' bit set and the + Result-Code AVP set to DIAMETER_REDIRECT_INDICATION uses the Hop-by- + Hop Identifier in the Diameter header to identify the request in the + pending message queue (see Section 5.5.4) that is to be redirected. + If no transport connection exists with the new peer, one is created, + and the request is sent directly to it. + + Multiple Redirect-Host AVPs are allowed. The receiver of the answer + message with the 'E' bit set selects exactly one of these hosts as + the destination of the redirected message. + + When the Redirect-Host-Usage AVP included in the answer message has a + non-zero value, a route entry for the redirect indications is created + and cached by the receiver. The redirect usage for such a route + entry is set by the value of Redirect-Host-Usage AVP and the lifetime + of the cached route entry is set by Redirect-Max-Cache-Time AVP + value. + + It is possible that multiple redirect indications can create multiple + cached route entries differing only in their redirect usage and the + peer to forward messages to. As an example, two(2) route entries + that are created by two(2) redirect indications results in two(2) + + + +Fajardo, et al. Standards Track [Page 78] + +RFC 6733 Diameter Base Protocol October 2012 + + + cached routes for the same realm and Application Id. However, one + has a redirect usage of ALL_SESSION, where matching requests will be + forwarded to one peer; the other has a redirect usage of ALL_REALM, + where request are forwarded to another peer. Therefore, an incoming + request that matches the realm and Application Id of both routes will + need additional resolution. In such a case, a routing precedence + rule MUST be used against the redirect usage value to resolve the + contention. The precedence rule can be found in Section 6.13. + +6.1.9. Relaying and Proxying Requests + + A relay or proxy agent MUST append a Route-Record AVP to all requests + forwarded. The AVP contains the identity of the peer from which the + request was received. + + The Hop-by-Hop Identifier in the request is saved and replaced with a + locally unique value. The source of the request is also saved, which + includes the IP address, port, and protocol. + + A relay or proxy agent MAY include the Proxy-Info AVP in requests if + it requires access to any local state information when the + corresponding response is received. The Proxy-Info AVP has security + implications as state information is distributed to other entities. + As such, it is RECOMMENDED that the content of the Proxy-Info AVP be + protected with cryptographic mechanisms, for example, by using a + keyed message digest such as HMAC-SHA1 [RFC2104]. Such a mechanism, + however, requires the management of keys, although only locally at + the Diameter server. Still, a full description of the management of + the keys used to protect the Proxy-Info AVP is beyond the scope of + this document. Below is a list of common recommendations: + + o The keys should be generated securely following the randomness + recommendations in [RFC4086]. + + o The keys and cryptographic protection algorithms should be at + least 128 bits in strength. + + o The keys should not be used for any other purpose than generating + and verifying instances of the Proxy-Info AVP. + + o The keys should be changed regularly. + + o The keys should be changed if the AVP format or cryptographic + protection algorithms change. + + The message is then forwarded to the next hop, as identified in the + routing table. + + + + +Fajardo, et al. Standards Track [Page 79] + +RFC 6733 Diameter Base Protocol October 2012 + + + Figure 6 provides an example of message routing using the procedures + listed in these sections. + + (Origin-Host=nas.example.net) (Origin-Host=nas.example.net) + (Origin-Realm=example.net) (Origin-Realm=example.net) + (Destination-Realm=example.com) (Destination-Realm=example.com) + (Route-Record=nas.example.net) + +------+ ------> +------+ ------> +------+ + | | (Request) | | (Request) | | + | NAS +-------------------+ DRL +-------------------+ HMS | + | | | | | | + +------+ <------ +------+ <------ +------+ + example.net (Answer) example.net (Answer) example.com + (Origin-Host=hms.example.com) (Origin-Host=hms.example.com) + (Origin-Realm=example.com) (Origin-Realm=example.com) + + Figure 6: Routing of Diameter messages + + Relay and proxy agents are not required to perform full inspection of + incoming messages. At a minimum, validation of the message header + and relevant routing AVPs has to be done when relaying messages. + Proxy agents may optionally perform more in-depth message validation + for applications in which it is interested. + +6.2. Diameter Answer Processing + + When a request is locally processed, the following procedures MUST be + applied to create the associated answer, in addition to any + additional procedures that MAY be discussed in the Diameter + application defining the command: + + o The same Hop-by-Hop Identifier in the request is used in the + answer. + + o The local host's identity is encoded in the Origin-Host AVP. + + o The Destination-Host and Destination-Realm AVPs MUST NOT be + present in the answer message. + + o The Result-Code AVP is added with its value indicating success or + failure. + + o If the Session-Id is present in the request, it MUST be included + in the answer. + + o Any Proxy-Info AVPs in the request MUST be added to the answer + message, in the same order they were present in the request. + + + + +Fajardo, et al. Standards Track [Page 80] + +RFC 6733 Diameter Base Protocol October 2012 + + + o The 'P' bit is set to the same value as the one in the request. + + o The same End-to-End identifier in the request is used in the + answer. + + Note that the error messages (see Section 7) are also subjected to + the above processing rules. + +6.2.1. Processing Received Answers + + A Diameter client or proxy MUST match the Hop-by-Hop Identifier in an + answer received against the list of pending requests. The + corresponding message should be removed from the list of pending + requests. It SHOULD ignore answers received that do not match a + known Hop-by-Hop Identifier. + +6.2.2. Relaying and Proxying Answers + + If the answer is for a request that was proxied or relayed, the agent + MUST restore the original value of the Diameter header's Hop-by-Hop + Identifier field. + + If the last Proxy-Info AVP in the message is targeted to the local + Diameter server, the AVP MUST be removed before the answer is + forwarded. + + If a relay or proxy agent receives an answer with a Result-Code AVP + indicating a failure, it MUST NOT modify the contents of the AVP. + Any additional local errors detected SHOULD be logged but not + reflected in the Result-Code AVP. If the agent receives an answer + message with a Result-Code AVP indicating success, and it wishes to + modify the AVP to indicate an error, it MUST modify the Result-Code + AVP to contain the appropriate error in the message destined towards + the access device as well as include the Error-Reporting-Host AVP; it + MUST also issue an STR on behalf of the access device towards the + Diameter server. + + The agent MUST then send the answer to the host that it received the + original request from. + +6.3. Origin-Host AVP + + The Origin-Host AVP (AVP Code 264) is of type DiameterIdentity, and + it MUST be present in all Diameter messages. This AVP identifies the + endpoint that originated the Diameter message. Relay agents MUST NOT + modify this AVP. + + + + + +Fajardo, et al. Standards Track [Page 81] + +RFC 6733 Diameter Base Protocol October 2012 + + + The value of the Origin-Host AVP is guaranteed to be unique within a + single host. + + Note that the Origin-Host AVP may resolve to more than one address as + the Diameter peer may support more than one address. + + This AVP SHOULD be placed as close to the Diameter header as + possible. + +6.4. Origin-Realm AVP + + The Origin-Realm AVP (AVP Code 296) is of type DiameterIdentity. + This AVP contains the Realm of the originator of any Diameter message + and MUST be present in all messages. + + This AVP SHOULD be placed as close to the Diameter header as + possible. + +6.5. Destination-Host AVP + + The Destination-Host AVP (AVP Code 293) is of type DiameterIdentity. + This AVP MUST be present in all unsolicited agent initiated messages, + MAY be present in request messages, and MUST NOT be present in answer + messages. + + The absence of the Destination-Host AVP will cause a message to be + sent to any Diameter server supporting the application within the + realm specified in Destination-Realm AVP. + + This AVP SHOULD be placed as close to the Diameter header as + possible. + +6.6. Destination-Realm AVP + + The Destination-Realm AVP (AVP Code 283) is of type DiameterIdentity + and contains the realm to which the message is to be routed. The + Destination-Realm AVP MUST NOT be present in answer messages. + Diameter clients insert the realm portion of the User-Name AVP. + Diameter servers initiating a request message use the value of the + Origin-Realm AVP from a previous message received from the intended + target host (unless it is known a priori). When present, the + Destination-Realm AVP is used to perform message routing decisions. + + The CCF for a request message that includes the Destination-Realm AVP + SHOULD list the Destination-Realm AVP as a required AVP (an AVP + indicated as {AVP}); otherwise, the message is inherently a non- + routable message. + + + + +Fajardo, et al. Standards Track [Page 82] + +RFC 6733 Diameter Base Protocol October 2012 + + + This AVP SHOULD be placed as close to the Diameter header as + possible. + +6.7. Routing AVPs + + The AVPs defined in this section are Diameter AVPs used for routing + purposes. These AVPs change as Diameter messages are processed by + agents. + +6.7.1. Route-Record AVP + + The Route-Record AVP (AVP Code 282) is of type DiameterIdentity. The + identity added in this AVP MUST be the same as the one received in + the Origin-Host of the Capabilities Exchange message. + +6.7.2. Proxy-Info AVP + + The Proxy-Info AVP (AVP Code 284) is of type Grouped. This AVP + contains the identity and local state information of the Diameter + node that creates and adds it to a message. The Grouped Data field + has the following CCF grammar: + + Proxy-Info ::= < AVP Header: 284 > + { Proxy-Host } + { Proxy-State } + * [ AVP ] + +6.7.3. Proxy-Host AVP + + The Proxy-Host AVP (AVP Code 280) is of type DiameterIdentity. This + AVP contains the identity of the host that added the Proxy-Info AVP. + +6.7.4. Proxy-State AVP + + The Proxy-State AVP (AVP Code 33) is of type OctetString. It + contains state information that would otherwise be stored at the + Diameter entity that created it. As such, this AVP MUST be treated + as opaque data by other Diameter entities. + +6.8. Auth-Application-Id AVP + + The Auth-Application-Id AVP (AVP Code 258) is of type Unsigned32 and + is used in order to advertise support of the Authentication and + Authorization portion of an application (see Section 2.4). If + present in a message other than CER and CEA, the value of the Auth- + Application-Id AVP MUST match the Application Id present in the + Diameter message header. + + + + +Fajardo, et al. Standards Track [Page 83] + +RFC 6733 Diameter Base Protocol October 2012 + + +6.9. Acct-Application-Id AVP + + The Acct-Application-Id AVP (AVP Code 259) is of type Unsigned32 and + is used in order to advertise support of the accounting portion of an + application (see Section 2.4). If present in a message other than + CER and CEA, the value of the Acct-Application-Id AVP MUST match the + Application Id present in the Diameter message header. + +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. + + The following values are supported: + + + NO_INBAND_SECURITY 0 + + This peer does not support TLS/TCP and DTLS/SCTP. This is the + default value, if the AVP is omitted. + + TLS 1 + + This node supports TLS/TCP [RFC5246] and DTLS/SCTP [RFC6083] + security. + +6.11. Vendor-Specific-Application-Id AVP + + The Vendor-Specific-Application-Id AVP (AVP Code 260) is of type + Grouped and is used to advertise support of a vendor-specific + Diameter application. Exactly one instance of either Auth- + Application-Id or Acct-Application-Id AVP MUST be present. The + Application Id carried by either Auth-Application-Id or Acct- + Application-Id AVP MUST comply with vendor-specific Application Id + assignment described in Section 11.3. It MUST also match the + Application Id present in the Diameter header except when used in a + CER or CEA message. + + The Vendor-Id AVP is an informational AVP pertaining to the vendor + who may have authorship of the vendor-specific Diameter application. + It MUST NOT be used as a means of defining a completely separate + vendor-specific Application Id space. + + + + + +Fajardo, et al. Standards Track [Page 84] + +RFC 6733 Diameter Base Protocol October 2012 + + + The Vendor-Specific-Application-Id AVP SHOULD be placed as close to + the Diameter header as possible. + + AVP Format + + <Vendor-Specific-Application-Id> ::= < AVP Header: 260 > + { Vendor-Id } + [ Auth-Application-Id ] + [ Acct-Application-Id ] + + A Vendor-Specific-Application-Id AVP MUST contain exactly one of + either Auth-Application-Id or Acct-Application-Id. If a Vendor- + Specific-Application-Id is received without one of these two AVPs, + then the recipient SHOULD issue an answer with a Result-Code set to + DIAMETER_MISSING_AVP. The answer SHOULD also include a Failed-AVP, + which MUST contain an example of an Auth-Application-Id AVP and an + Acct-Application-Id AVP. + + If a Vendor-Specific-Application-Id is received that contains both + Auth-Application-Id and Acct-Application-Id, then the recipient MUST + issue an answer with Result-Code set to + DIAMETER_AVP_OCCURS_TOO_MANY_TIMES. The answer MUST also include a + Failed-AVP, which MUST contain the received Auth-Application-Id AVP + and Acct-Application-Id AVP. + +6.12. Redirect-Host AVP + + The Redirect-Host AVP (AVP Code 292) is of type DiameterURI. One or + more instances of this AVP MUST be present if the answer message's + 'E' bit is set and the Result-Code AVP is set to + DIAMETER_REDIRECT_INDICATION. + + Upon receiving the above, the receiving Diameter node SHOULD forward + the request directly to one of the hosts identified in these AVPs. + The server contained in the selected Redirect-Host AVP SHOULD be used + for all messages matching the criteria set by the Redirect-Host-Usage + AVP. + +6.13. Redirect-Host-Usage AVP + + The Redirect-Host-Usage AVP (AVP Code 261) is of type Enumerated. + This AVP MAY be present in answer messages whose 'E' bit is set and + the Result-Code AVP is set to DIAMETER_REDIRECT_INDICATION. + + When present, this AVP provides hints about how the routing entry + resulting from the Redirect-Host is to be used. The following values + are supported: + + + + +Fajardo, et al. Standards Track [Page 85] + +RFC 6733 Diameter Base Protocol October 2012 + + + DONT_CACHE 0 + + The host specified in the Redirect-Host AVP SHOULD NOT be cached. + This is the default value. + + ALL_SESSION 1 + + All messages within the same session, as defined by the same value + of the Session-ID AVP SHOULD be sent to the host specified in the + Redirect-Host AVP. + + ALL_REALM 2 + + All messages destined for the realm requested SHOULD be sent to + the host specified in the Redirect-Host AVP. + + REALM_AND_APPLICATION 3 + + All messages for the application requested to the realm specified + SHOULD be sent to the host specified in the Redirect-Host AVP. + + ALL_APPLICATION 4 + + All messages for the application requested SHOULD be sent to the + host specified in the Redirect-Host AVP. + + ALL_HOST 5 + + All messages that would be sent to the host that generated the + Redirect-Host SHOULD be sent to the host specified in the + Redirect-Host AVP. + + ALL_USER 6 + + All messages for the user requested SHOULD be sent to the host + specified in the Redirect-Host AVP. + + When multiple cached routes are created by redirect indications and + they differ only in redirect usage and peers to forward requests to + (see Section 6.1.8), a precedence rule MUST be applied to the + redirect usage values of the cached routes during normal routing to + resolve contentions that may occur. The precedence rule is the order + that dictate which redirect usage should be considered before any + other as they appear. The order is as follows: + + + + + + + +Fajardo, et al. Standards Track [Page 86] + +RFC 6733 Diameter Base Protocol October 2012 + + + 1. ALL_SESSION + + 2. ALL_USER + + 3. REALM_AND_APPLICATION + + 4. ALL_REALM + + 5. ALL_APPLICATION + + 6. ALL_HOST + +6.14. Redirect-Max-Cache-Time AVP + + The Redirect-Max-Cache-Time AVP (AVP Code 262) is of type Unsigned32. + This AVP MUST be present in answer messages whose 'E' bit is set, + whose Result-Code AVP is set to DIAMETER_REDIRECT_INDICATION, and + whose Redirect-Host-Usage AVP set to a non-zero value. + + This AVP contains the maximum number of seconds the peer and route + table entries, created as a result of the Redirect-Host, SHOULD be + cached. Note that once a host is no longer reachable, any associated + cache, peer, and routing table entries MUST be deleted. + +7. Error Handling + + There are two different types of errors in Diameter; protocol errors + and application errors. A protocol error is one that occurs at the + base protocol level and MAY require per-hop attention (e.g., a + message routing error). Application errors, on the other hand, + generally occur due to a problem with a function specified in a + Diameter application (e.g., user authentication, missing AVP). + + Result-Code AVP values that are used to report protocol errors MUST + only be present in answer messages whose 'E' bit is set. When a + request message is received that causes a protocol error, an answer + message is returned with the 'E' bit set, and the Result-Code AVP is + set to the appropriate protocol error value. As the answer is sent + back towards the originator of the request, each proxy or relay agent + MAY take action on the message. + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 87] + +RFC 6733 Diameter Base Protocol October 2012 + + + 1. Request +---------+ Link Broken + +-------------------------->|Diameter |----///----+ + | +---------------------| | v + +------+--+ | 2. answer + 'E' set | Relay 2 | +--------+ + |Diameter |<-+ (Unable to Forward) +---------+ |Diameter| + | | | Home | + | Relay 1 |--+ +---------+ | Server | + +---------+ | 3. Request |Diameter | +--------+ + +-------------------->| | ^ + | Relay 3 |-----------+ + +---------+ + + Figure 7: Example of Protocol Error Causing Answer Message + + Figure 7 provides an example of a message forwarded upstream by a + Diameter relay. When the message is received by Relay 2, and it + detects that it cannot forward the request to the home server, an + answer message is returned with the 'E' bit set and the Result-Code + AVP set to DIAMETER_UNABLE_TO_DELIVER. Given that this error falls + within the protocol error category, Relay 1 would take special + action, and given the error, attempt to route the message through its + alternate Relay 3. + + +---------+ 1. Request +---------+ 2. Request +---------+ + | Access |------------>|Diameter |------------>|Diameter | + | | | | | Home | + | Device |<------------| Relay |<------------| Server | + +---------+ 4. Answer +---------+ 3. Answer +---------+ + (Missing AVP) (Missing AVP) + + Figure 8: Example of Application Error Answer Message + + Figure 8 provides an example of a Diameter message that caused an + application error. When application errors occur, the Diameter + entity reporting the error clears the 'R' bit in the Command Flags + and adds the Result-Code AVP with the proper value. Application + errors do not require any proxy or relay agent involvement; + therefore, the message would be forwarded back to the originator of + the request. + + In the case where the answer message itself contains errors, any + related session SHOULD be terminated by sending an STR or ASR + message. The Termination-Cause AVP in the STR MAY be filled with the + appropriate value to indicate the cause of the error. An application + MAY also send an application-specific request instead of an STR or + ASR message to signal the error in the case where no state is + maintained or to allow for some form of error recovery with the + corresponding Diameter entity. + + + +Fajardo, et al. Standards Track [Page 88] + +RFC 6733 Diameter Base Protocol October 2012 + + + There are certain Result-Code AVP application errors that require + additional AVPs to be present in the answer. In these cases, the + Diameter node that sets the Result-Code AVP to indicate the error + MUST add the AVPs. Examples are as follows: + + o A request with an unrecognized AVP is received with the 'M' bit + (Mandatory bit) set causes an answer to be sent with the Result- + Code AVP set to DIAMETER_AVP_UNSUPPORTED and the Failed-AVP AVP + containing the offending AVP. + + o A request with an AVP that is received with an unrecognized value + causes an answer to be returned with the Result-Code AVP set to + DIAMETER_INVALID_AVP_VALUE, with the Failed-AVP AVP containing the + AVP causing the error. + + o A received command that is missing AVPs that are defined as + required in the commands CCF; examples are AVPs indicated as + {AVP}. The receiver issues an answer with the Result-Code set to + DIAMETER_MISSING_AVP and creates an AVP with the AVP Code and + other fields set as expected in the missing AVP. The created AVP + is then added to the Failed-AVP AVP. + + The Result-Code AVP describes the error that the Diameter node + encountered in its processing. In case there are multiple errors, + the Diameter node MUST report only the first error it encountered + (detected possibly in some implementation-dependent order). The + specific errors that can be described by this AVP are described in + the following section. + +7.1. Result-Code AVP + + The Result-Code AVP (AVP Code 268) is of type Unsigned32 and + indicates whether a particular request was completed successfully or + an error occurred. All Diameter answer messages in IETF-defined + Diameter application specifications MUST include one Result-Code AVP. + A non-successful Result-Code AVP (one containing a non-2xxx value + other than DIAMETER_REDIRECT_INDICATION) MUST include the Error- + Reporting-Host AVP if the host setting the Result-Code AVP is + different from the identity encoded in the Origin-Host AVP. + + The Result-Code data field contains an IANA-managed 32-bit address + space representing errors (see Section 11.3.2). Diameter provides + the following classes of errors, all identified by the thousands + digit in the decimal notation: + + + + + + + +Fajardo, et al. Standards Track [Page 89] + +RFC 6733 Diameter Base Protocol October 2012 + + + o 1xxx (Informational) + + o 2xxx (Success) + + o 3xxx (Protocol Errors) + + o 4xxx (Transient Failures) + + o 5xxx (Permanent Failure) + + An unrecognized class (one whose first digit is not defined in this + section) MUST be handled as a permanent failure. + +7.1.1. Informational + + Errors that fall within this category are used to inform the + requester that a request could not be satisfied, and additional + action is required on its part before access is granted. + + DIAMETER_MULTI_ROUND_AUTH 1001 + + This informational error is returned by a Diameter server to + inform the access device that the authentication mechanism being + used requires multiple round trips, and a subsequent request needs + to be issued in order for access to be granted. + +7.1.2. Success + + Errors that fall within the Success category are used to inform a + peer that a request has been successfully completed. + + DIAMETER_SUCCESS 2001 + + The request was successfully completed. + + DIAMETER_LIMITED_SUCCESS 2002 + + When returned, the request was successfully completed, but + additional processing is required by the application in order to + provide service to the user. + +7.1.3. Protocol Errors + + Errors that fall within the Protocol Error category SHOULD be treated + on a per-hop basis, and Diameter proxies MAY attempt to correct the + error, if it is possible. Note that these errors MUST only be used + in answer messages whose 'E' bit is set. + + + + +Fajardo, et al. Standards Track [Page 90] + +RFC 6733 Diameter Base Protocol October 2012 + + + DIAMETER_COMMAND_UNSUPPORTED 3001 + + This error code is used when a Diameter entity receives a message + with a Command Code that it does not support. + + DIAMETER_UNABLE_TO_DELIVER 3002 + + This error is given when Diameter cannot deliver the message to + the destination, either because no host within the realm + supporting the required application was available to process the + request or because the Destination-Host AVP was given without the + associated Destination-Realm AVP. + + DIAMETER_REALM_NOT_SERVED 3003 + + The intended realm of the request is not recognized. + + DIAMETER_TOO_BUSY 3004 + + When returned, a Diameter node SHOULD attempt to send the message + to an alternate peer. This error MUST only be used when a + specific server is requested, and it cannot provide the requested + service. + + DIAMETER_LOOP_DETECTED 3005 + + An agent detected a loop while trying to get the message to the + intended recipient. The message MAY be sent to an alternate peer, + if one is available, but the peer reporting the error has + identified a configuration problem. + + DIAMETER_REDIRECT_INDICATION 3006 + + A redirect agent has determined that the request could not be + satisfied locally, and the initiator of the request SHOULD direct + the request directly to the server, whose contact information has + been added to the response. When set, the Redirect-Host AVP MUST + be present. + + DIAMETER_APPLICATION_UNSUPPORTED 3007 + + A request was sent for an application that is not supported. + + DIAMETER_INVALID_HDR_BITS 3008 + + A request was received whose bits in the Diameter header were set + either to an invalid combination or to a value that is + inconsistent with the Command Code's definition. + + + +Fajardo, et al. Standards Track [Page 91] + +RFC 6733 Diameter Base Protocol October 2012 + + + DIAMETER_INVALID_AVP_BITS 3009 + + A request was received that included an AVP whose flag bits are + set to an unrecognized value or that is inconsistent with the + AVP's definition. + + DIAMETER_UNKNOWN_PEER 3010 + + A CER was received from an unknown peer. + +7.1.4. Transient Failures + + Errors that fall within the transient failures category are used to + inform a peer that the request could not be satisfied at the time it + was received but MAY be able to satisfy the request in the future. + Note that these errors MUST be used in answer messages whose 'E' bit + is not set. + + DIAMETER_AUTHENTICATION_REJECTED 4001 + + The authentication process for the user failed, most likely due to + an invalid password used by the user. Further attempts MUST only + be tried after prompting the user for a new password. + + DIAMETER_OUT_OF_SPACE 4002 + + A Diameter node received the accounting request but was unable to + commit it to stable storage due to a temporary lack of space. + + ELECTION_LOST 4003 + + The peer has determined that it has lost the election process and + has therefore disconnected the transport connection. + +7.1.5. Permanent Failures + + Errors that fall within the permanent failures category are used to + inform the peer that the request failed and should not be attempted + again. Note that these errors SHOULD be used in answer messages + whose 'E' bit is not set. In error conditions where it is not + possible or efficient to compose application-specific answer grammar, + answer messages with the 'E' bit set and which comply to the grammar + described in Section 7.2 MAY also be used for permanent errors. + + + + + + + + +Fajardo, et al. Standards Track [Page 92] + +RFC 6733 Diameter Base Protocol October 2012 + + + DIAMETER_AVP_UNSUPPORTED 5001 + + The peer received a message that contained an AVP that is not + recognized or supported and was marked with the 'M' (Mandatory) + bit. A Diameter message with this error MUST contain one or more + Failed-AVP AVPs containing the AVPs that caused the failure. + + DIAMETER_UNKNOWN_SESSION_ID 5002 + + The request contained an unknown Session-Id. + + DIAMETER_AUTHORIZATION_REJECTED 5003 + + A request was received for which the user could not be authorized. + This error could occur if the service requested is not permitted + to the user. + + DIAMETER_INVALID_AVP_VALUE 5004 + + 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. + + DIAMETER_MISSING_AVP 5005 + + The request did not contain an AVP that is required by the Command + Code definition. If this value is sent in the Result-Code AVP, a + Failed-AVP AVP SHOULD be included in the message. The Failed-AVP + AVP MUST contain an example of the missing AVP complete with the + Vendor-Id if applicable. The value field of the missing AVP + should be of correct minimum length and contain zeroes. + + DIAMETER_RESOURCES_EXCEEDED 5006 + + A request was received that cannot be authorized because the user + has already expended allowed resources. An example of this error + condition is when a user that is restricted to one dial-up PPP + port attempts to establish a second PPP connection. + + DIAMETER_CONTRADICTING_AVPS 5007 + + The Home Diameter server has detected AVPs in the request that + contradicted each other, and it is not willing to provide service + to the user. The Failed-AVP AVP MUST be present, which contain + the AVPs that contradicted each other. + + + + + + +Fajardo, et al. Standards Track [Page 93] + +RFC 6733 Diameter Base Protocol October 2012 + + + DIAMETER_AVP_NOT_ALLOWED 5008 + + 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. + + DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009 + + A message was received that included an AVP that appeared more + often than permitted in the message definition. The Failed-AVP + AVP MUST be included and contain a copy of the first instance of + the offending AVP that exceeded the maximum number of occurrences. + + DIAMETER_NO_COMMON_APPLICATION 5010 + + This error is returned by a Diameter node that receives a CER + whereby no applications are common between the CER sending peer + and the CER receiving peer. + + DIAMETER_UNSUPPORTED_VERSION 5011 + + This error is returned when a request was received, whose version + number is unsupported. + + DIAMETER_UNABLE_TO_COMPLY 5012 + + This error is returned when a request is rejected for unspecified + reasons. + + DIAMETER_INVALID_BIT_IN_HEADER 5013 + + This error is returned when a reserved bit in the Diameter header + is set to one (1) or the bits in the Diameter header are set + incorrectly. + + 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 + + + + +Fajardo, et al. Standards Track [Page 94] + +RFC 6733 Diameter Base Protocol October 2012 + + + 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. + + DIAMETER_INVALID_MESSAGE_LENGTH 5015 + + This error is returned when a request is received with an invalid + message length. + + DIAMETER_INVALID_AVP_BIT_COMBO 5016 + + The request contained an AVP with which is not allowed to have the + given value in the AVP Flags field. A Diameter message indicating + this error MUST include the offending AVPs within a Failed-AVP + AVP. + + DIAMETER_NO_COMMON_SECURITY 5017 + + This error is returned when a CER message is received, and there + are no common security mechanisms supported between the peers. A + Capabilities-Exchange-Answer (CEA) message MUST be returned with + the Result-Code AVP set to DIAMETER_NO_COMMON_SECURITY. + +7.2. Error Bit + + The 'E' (Error Bit) in the Diameter header is set when the request + caused a protocol-related error (see Section 7.1.3). A message with + the 'E' bit MUST NOT be sent as a response to an answer message. + Note that a message with the 'E' bit set is still subjected to the + processing rules defined in Section 6.2. When set, the answer + message will not conform to the CCF specification for the command; + instead, it and will conform to the following CCF: + + Message Format + + <answer-message> ::= < Diameter Header: code, ERR [, PXY] > + 0*1< Session-Id > + { Origin-Host } + { Origin-Realm } + { Result-Code } + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Experimental-Result ] + * [ Proxy-Info ] + * [ AVP ] + + + + +Fajardo, et al. Standards Track [Page 95] + +RFC 6733 Diameter Base Protocol October 2012 + + + Note that the code used in the header is the same than the one found + in the request message, but with the 'R' bit cleared and the 'E' bit + set. The 'P' bit in the header is set to the same value as the one + found in the request message. + +7.3. Error-Message AVP + + The Error-Message AVP (AVP Code 281) is of type UTF8String. It MAY + accompany a Result-Code AVP as a human-readable error message. The + Error-Message AVP is not intended to be useful in an environment + where error messages are processed automatically. It SHOULD NOT be + expected that the content of this AVP be parsed by network entities. + +7.4. Error-Reporting-Host AVP + + The Error-Reporting-Host AVP (AVP Code 294) is of type + DiameterIdentity. This AVP contains the identity of the Diameter + host that sent the Result-Code AVP to a value other than 2001 + (Success), only if the host setting the Result-Code is different from + the one encoded in the Origin-Host AVP. This AVP is intended to be + used for troubleshooting purposes, and it MUST be set when the + Result-Code AVP indicates a failure. + +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. + + The possible reasons for this AVP are the presence of an improperly + constructed AVP, an unsupported or unrecognized AVP, an invalid AVP + value, the omission of a required AVP, the presence of an explicitly + excluded AVP (see tables in Section 10) or the presence of two or + more occurrences of an AVP that is restricted to 0, 1, or 0-1 + occurrences. + + A Diameter message SHOULD contain one Failed-AVP AVP, containing the + entire AVP that could not be processed successfully. If the failure + reason is omission of a required AVP, an AVP with the missing AVP + code, the missing Vendor-Id, and a zero-filled payload of the minimum + required length for the omitted AVP will be added. If the failure + reason is an invalid AVP length where the reported length is less + + + +Fajardo, et al. Standards Track [Page 96] + +RFC 6733 Diameter Base Protocol October 2012 + + + than the minimum AVP header length or greater than the reported + message length, a copy of the offending AVP header and a zero-filled + payload of the minimum required length SHOULD be added. + + In the case where the offending AVP is embedded within a Grouped AVP, + the Failed-AVP MAY contain the grouped AVP, which in turn contains + the single offending AVP. The same method MAY be employed if the + grouped AVP itself is embedded in yet another grouped AVP and so on. + In this case, the Failed-AVP MAY contain the grouped AVP hierarchy up + to the single offending AVP. This enables the recipient to detect + the location of the offending AVP when embedded in a group. + + AVP Format + + <Failed-AVP> ::= < AVP Header: 279 > + 1* {AVP} + +7.6. Experimental-Result AVP + + The Experimental-Result AVP (AVP Code 297) is of type Grouped, and + indicates whether a particular vendor-specific request was completed + successfully or whether an error occurred. This AVP has the + following structure: + + AVP Format + + Experimental-Result ::= < AVP Header: 297 > + { Vendor-Id } + { Experimental-Result-Code } + + The Vendor-Id AVP (see Section 5.3.3) in this grouped AVP identifies + the vendor responsible for the assignment of the result code that + follows. All Diameter answer messages defined in vendor-specific + applications MUST include either one Result-Code AVP or one + Experimental-Result AVP. + +7.7. Experimental-Result-Code AVP + + The Experimental-Result-Code AVP (AVP Code 298) is of type Unsigned32 + and contains a vendor-assigned value representing the result of + processing the request. + + It is recommended that vendor-specific result codes follow the same + conventions given for the Result-Code AVP regarding the different + types of result codes and the handling of errors (for non-2xxx + values). + + + + + +Fajardo, et al. Standards Track [Page 97] + +RFC 6733 Diameter Base Protocol October 2012 + + +8. Diameter User Sessions + + In general, Diameter can provide two different types of services to + applications. The first involves authentication and authorization, + and it can optionally make use of accounting. The second only makes + use of accounting. + + When a service makes use of the authentication and/or authorization + portion of an application, and a user requests access to the network, + the Diameter client issues an auth request to its local server. The + auth request is defined in a service-specific Diameter application + (e.g., NASREQ). The request contains a Session-Id AVP, which is used + in subsequent messages (e.g., subsequent authorization, accounting, + etc.) relating to the user's session. The Session-Id AVP is a means + for the client and servers to correlate a Diameter message with a + user session. + + When a Diameter server authorizes a user to implement network + resources for a finite amount of time, and it is willing to extend + the authorization via a future request, it MUST add the + Authorization- Lifetime AVP to the answer message. The + Authorization-Lifetime AVP defines the maximum number of seconds a + user MAY make use of the resources before another authorization + request is expected by the server. The Auth-Grace-Period AVP + contains the number of seconds following the expiration of the + Authorization-Lifetime, after which the server will release all state + information related to the user's session. Note that if payment for + services is expected by the serving realm from the user's home realm, + the Authorization-Lifetime AVP, combined with the Auth-Grace-Period + AVP, implies the maximum length of the session for which the home + realm is willing to be fiscally responsible. Services provided past + the expiration of the Authorization-Lifetime and Auth-Grace-Period + AVPs are the responsibility of the access device. Of course, the + actual cost of services rendered is clearly outside the scope of the + protocol. + + An access device that does not expect to send a re-authorization or a + session termination request to the server MAY include the Auth- + Session-State AVP with the value set to NO_STATE_MAINTAINED as a hint + to the server. If the server accepts the hint, it agrees that since + no session termination message will be received once service to the + user is terminated, it cannot maintain state for the session. If the + answer message from the server contains a different value in the + Auth-Session-State AVP (or the default value if the AVP is absent), + the access device MUST follow the server's directives. Note that the + value NO_STATE_MAINTAINED MUST NOT be set in subsequent re- + authorization requests and answers. + + + + +Fajardo, et al. Standards Track [Page 98] + +RFC 6733 Diameter Base Protocol October 2012 + + + The base protocol does not include any authorization request + messages, since these are largely application-specific and are + defined in a Diameter application document. However, the base + protocol does define a set of messages that are used to terminate + user sessions. These are used to allow servers that maintain state + information to free resources. + + When a service only makes use of the accounting portion of the + Diameter protocol, even in combination with an application, the + Session-Id is still used to identify user sessions. However, the + session termination messages are not used, since a session is + signaled as being terminated by issuing an accounting stop message. + + Diameter may also be used for services that cannot be easily + categorized as authentication, authorization, or accounting (e.g., + certain Third Generation Partnership Project Internet Multimedia + System (3GPP IMS) interfaces). In such cases, the finite state + machine defined in subsequent sections may not be applicable. + Therefore, the application itself MAY need to define its own finite + state machine. However, such application-specific state machines + SHOULD follow the general state machine framework outlined in this + document such as the use of Session-Id AVPs and the use of STR/STA, + ASR/ASA messages for stateful sessions. + +8.1. Authorization Session State Machine + + This section contains a set of finite state machines, which represent + the life cycle of Diameter sessions and which MUST be observed by all + Diameter implementations that make use of the authentication and/or + authorization portion of a Diameter application. The term "Service- + Specific" below refers to a message defined in a Diameter application + (e.g., Mobile IPv4, NASREQ). + + There are four different authorization session state machines + supported in the Diameter base protocol. The first two describe a + session in which the server is maintaining session state, indicated + by the value of the Auth-Session-State AVP (or its absence). One + describes the session from a client perspective, the other from a + server perspective. The second two state machines are used when the + server does not maintain session state. Here again, one describes + the session from a client perspective, the other from a server + perspective. + + When a session is moved to the Idle state, any resources that were + allocated for the particular session must be released. Any event not + listed in the state machines MUST be considered an error condition, + and an answer, if applicable, MUST be returned to the originator of + the message. + + + +Fajardo, et al. Standards Track [Page 99] + +RFC 6733 Diameter Base Protocol October 2012 + + + In the case that an application does not support re-auth, the state + transitions related to server-initiated re-auth, when both client and + server sessions maintain state (e.g., Send RAR, Pending, Receive + RAA), MAY be ignored. + + In the state table, the event "Failure to send X" means that the + Diameter agent is unable to send command X to the desired + destination. This could be due to the peer being down or due to the + peer sending back a transient failure or temporary protocol error + notification DIAMETER_TOO_BUSY or DIAMETER_LOOP_DETECTED in the + Result-Code AVP of the corresponding Answer command. The event 'X + successfully sent' is the complement of 'Failure to send X'. + + The following state machine is observed by a client when state is + maintained on the server: + + CLIENT, STATEFUL + State Event Action New State + --------------------------------------------------------------- + Idle Client or device requests Send Pending + access service- + specific + auth req + + Idle ASR Received Send ASA Idle + for unknown session with + Result-Code = + UNKNOWN_ + SESSION_ID + + Idle RAR Received Send RAA Idle + for unknown session with + Result-Code = + UNKNOWN_ + SESSION_ID + + Pending Successful service-specific Grant Open + authorization answer Access + received with default + Auth-Session-State value + + Pending Successful service-specific Sent STR Discon + authorization answer received, + but service not provided + + Pending Error processing successful Sent STR Discon + service-specific authorization + answer + + + +Fajardo, et al. Standards Track [Page 100] + +RFC 6733 Diameter Base Protocol October 2012 + + + Pending Failed service-specific Clean up Idle + authorization answer received + + Open User or client device Send Open + requests access to service service- + specific + auth req + + Open Successful service-specific Provide Open + authorization answer received service + + Open Failed service-specific Discon. Idle + authorization answer user/device + received. + + Open RAR received and client will Send RAA Open + perform subsequent re-auth with + Result-Code = + SUCCESS + + Open RAR received and client will Send RAA Idle + not perform subsequent with + re-auth Result-Code != + SUCCESS, + Discon. + user/device + + Open Session-Timeout expires on Send STR Discon + access device + + Open ASR received, Send ASA Discon + client will comply with + with request to end the Result-Code = + session = SUCCESS, + Send STR. + + Open ASR Received, Send ASA Open + client will not comply with + with request to end the Result-Code != + session != SUCCESS + + Open Authorization-Lifetime + Send STR Discon + Auth-Grace-Period expires on + access device + + Discon ASR received Send ASA Discon + + + + + +Fajardo, et al. Standards Track [Page 101] + +RFC 6733 Diameter Base Protocol October 2012 + + + Discon STA received Discon. Idle + user/device + + The following state machine is observed by a server when it is + maintaining state for the session: + + SERVER, STATEFUL + State Event Action New State + --------------------------------------------------------------- + Idle Service-specific authorization Send Open + request received, and successful + user is authorized service- + specific + answer + + Idle Service-specific authorization Send Idle + request received, and failed + user is not authorized service- + specific + answer + + Open Service-specific authorization Send Open + request received, and user successful + is authorized service- + specific + answer + + Open Service-specific authorization Send Idle + request received, and user failed + is not authorized service- + specific + answer, + Clean up + + Open Home server wants to confirm Send RAR Pending + authentication and/or + authorization of the user + + Pending Received RAA with a failed Clean up Idle + Result-Code + + Pending Received RAA with Result-Code Update Open + = SUCCESS session + + Open Home server wants to Send ASR Discon + terminate the service + + + + + +Fajardo, et al. Standards Track [Page 102] + +RFC 6733 Diameter Base Protocol October 2012 + + + Open Authorization-Lifetime (and Clean up Idle + Auth-Grace-Period) expires + on home server + + Open Session-Timeout expires on Clean up Idle + home server + + Discon Failure to send ASR Wait, Discon + resend ASR + + Discon ASR successfully sent and Clean up Idle + ASA Received with Result-Code + + Not ASA Received None No Change + Discon + + Any STR Received Send STA, Idle + Clean up + + The following state machine is observed by a client when state is not + maintained on the server: + + CLIENT, STATELESS + State Event Action New State + --------------------------------------------------------------- + Idle Client or device requests Send Pending + access service- + specific + auth req + + Pending Successful service-specific Grant Open + authorization answer access + received with Auth-Session- + State set to + NO_STATE_MAINTAINED + + Pending Failed service-specific Clean up Idle + authorization answer + received + + Open Session-Timeout expires on Discon. Idle + access device user/device + + Open Service to user is terminated Discon. Idle + user/device + + + + + + +Fajardo, et al. Standards Track [Page 103] + +RFC 6733 Diameter Base Protocol October 2012 + + + The following state machine is observed by a server when it is not + maintaining state for the session: + + SERVER, STATELESS + State Event Action New State + --------------------------------------------------------------- + Idle Service-specific authorization Send Idle + request received, and service- + successfully processed specific + answer + +8.2. Accounting Session State Machine + + The following state machines MUST be supported for applications that + have an accounting portion or that require only accounting services. + The first state machine is to be observed by clients. + + See Section 9.7 for Accounting Command Codes and Section 9.8 for + Accounting AVPs. + + The server side in the accounting state machine depends in some cases + on the particular application. The Diameter base protocol defines a + default state machine that MUST be followed by all applications that + have not specified other state machines. This is the second state + machine in this section described below. + + The default server side state machine requires the reception of + accounting records in any order and at any time, and it does not + place any standards requirement on the processing of these records. + Implementations of Diameter may perform checking, ordering, + correlation, fraud detection, and other tasks based on these records. + AVPs may need to be inspected as a part of these tasks. The tasks + can happen either immediately after record reception or in a post- + processing phase. However, as these tasks are typically application + or even policy dependent, they are not standardized by the Diameter + specifications. Applications MAY define requirements on when to + accept accounting records based on the used value of Accounting- + Realtime-Required AVP, credit-limit checks, and so on. + + However, the Diameter base protocol defines one optional server side + state machine that MAY be followed by applications that require + keeping track of the session state at the accounting server. Note + that such tracking is incompatible with the ability to sustain long + duration connectivity problems. Therefore, the use of this state + machine is recommended only in applications where the value of the + Accounting-Realtime-Required AVP is DELIVER_AND_GRANT; hence, + accounting connectivity problems are required to cause the serviced + user to be disconnected. Otherwise, records produced by the client + + + +Fajardo, et al. Standards Track [Page 104] + +RFC 6733 Diameter Base Protocol October 2012 + + + may be lost by the server, which no longer accepts them after the + connectivity is re-established. This state machine is the third + state machine in this section. The state machine is supervised by a + supervision session timer Ts, whose value should be reasonably higher + than the Acct_Interim_Interval value. Ts MAY be set to two times the + value of the Acct_Interim_Interval so as to avoid the accounting + session in the Diameter server to change to Idle state in case of + short transient network failure. + + Any event not listed in the state machines MUST be considered as an + error condition, and a corresponding answer, if applicable, MUST be + returned to the originator of the message. + + In the state table, the event "Failure to send" means that the + Diameter client is unable to communicate with the desired + destination. This could be due to the peer being down, or due to the + peer sending back a transient failure or temporary protocol error + notification DIAMETER_OUT_OF_SPACE, DIAMETER_TOO_BUSY, or + DIAMETER_LOOP_DETECTED in the Result-Code AVP of the Accounting + Answer command. + + The event "Failed answer" means that the Diameter client received a + non-transient failure notification in the Accounting Answer command. + + Note that the action "Disconnect user/dev" MUST also have an effect + on the authorization session state table, e.g., cause the STR message + to be sent, if the given application has both authentication/ + authorization and accounting portions. + + The states PendingS, PendingI, PendingL, PendingE, and PendingB stand + for pending states to wait for an answer to an accounting request + related to a Start, Interim, Stop, Event, or buffered record, + respectively. + + CLIENT, ACCOUNTING + State Event Action New State + --------------------------------------------------------------- + Idle Client or device requests Send PendingS + access accounting + start req. + + Idle Client or device requests Send PendingE + a one-time service accounting + event req + + Idle Records in storage Send PendingB + record + + + + +Fajardo, et al. Standards Track [Page 105] + +RFC 6733 Diameter Base Protocol October 2012 + + + PendingS Successful accounting Open + start answer received + + PendingS Failure to send and buffer Store Open + space available and real time Start + not equal to DELIVER_AND_GRANT Record + + PendingS Failure to send and no buffer Open + space available and real time + equal to GRANT_AND_LOSE + + PendingS Failure to send and no Disconnect Idle + buffer space available and user/dev + real time not equal to + GRANT_AND_LOSE + + PendingS Failed accounting start answer Open + received and real time equal + to GRANT_AND_LOSE + + PendingS Failed accounting start answer Disconnect Idle + received and real time not user/dev + equal to GRANT_AND_LOSE + + PendingS User service terminated Store PendingS + stop + record + + Open Interim interval elapses Send PendingI + accounting + interim + record + + Open User service terminated Send PendingL + accounting + stop req. + + PendingI Successful accounting interim Open + answer received + + PendingI Failure to send and (buffer Store Open + space available or old interim + record can be overwritten) record + and real time not equal to + DELIVER_AND_GRANT + + + + + + +Fajardo, et al. Standards Track [Page 106] + +RFC 6733 Diameter Base Protocol October 2012 + + + PendingI Failure to send and no buffer Open + space available and real time + equal to GRANT_AND_LOSE + + PendingI Failure to send and no Disconnect Idle + buffer space available and user/dev + real time not equal to + GRANT_AND_LOSE + + PendingI Failed accounting interim Open + answer received and real time + equal to GRANT_AND_LOSE + + PendingI Failed accounting interim Disconnect Idle + answer received and user/dev + real time not equal to + GRANT_AND_LOSE + + PendingI User service terminated Store PendingI + stop + record + PendingE Successful accounting Idle + event answer received + + PendingE Failure to send and buffer Store Idle + space available event + record + + PendingE Failure to send and no buffer Idle + space available + + PendingE Failed accounting event answer Idle + received + + PendingB Successful accounting answer Delete Idle + received record + + PendingB Failure to send Idle + + PendingB Failed accounting answer Delete Idle + received record + + PendingL Successful accounting Idle + stop answer received + + PendingL Failure to send and buffer Store Idle + space available stop + record + + + +Fajardo, et al. Standards Track [Page 107] + +RFC 6733 Diameter Base Protocol October 2012 + + + PendingL Failure to send and no buffer Idle + space available + + PendingL Failed accounting stop answer Idle + received + + + SERVER, STATELESS ACCOUNTING + State Event Action New State + --------------------------------------------------------------- + + Idle Accounting start request Send Idle + received and successfully accounting + processed. start + answer + + Idle Accounting event request Send Idle + received and successfully accounting + processed. event + answer + + Idle Interim record received Send Idle + and successfully processed. accounting + interim + answer + + Idle Accounting stop request Send Idle + received and successfully accounting + processed stop answer + + Idle Accounting request received; Send Idle + no space left to store accounting + records answer; + Result-Code = + OUT_OF_ + SPACE + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 108] + +RFC 6733 Diameter Base Protocol October 2012 + + + SERVER, STATEFUL ACCOUNTING + State Event Action New State + --------------------------------------------------------------- + + Idle Accounting start request Send Open + received and successfully accounting + processed. start + answer; + Start Ts + + Idle Accounting event request Send Idle + received and successfully accounting + processed. event + answer + Idle Accounting request received; Send Idle + no space left to store accounting + records answer; + Result-Code = + OUT_OF_ + SPACE + + Open Interim record received Send Open + and successfully processed. accounting + interim + answer; + Restart Ts + + Open Accounting stop request Send Idle + received and successfully accounting + processed stop answer; + Stop Ts + + Open Accounting request received; Send Idle + no space left to store accounting + records answer; + Result-Code = + OUT_OF_ + SPACE; + Stop Ts + + Open Session supervision timer Ts Stop Ts Idle + expired + + + + + + + + + +Fajardo, et al. Standards Track [Page 109] + +RFC 6733 Diameter Base Protocol October 2012 + + +8.3. Server-Initiated Re-Auth + + A Diameter server may initiate a re-authentication and/or re- + authorization service for a particular session by issuing a Re-Auth- + Request (RAR). + + For example, for prepaid services, the Diameter server that + originally authorized a session may need some confirmation that the + user is still using the services. + + An access device that receives an RAR message with the Session-Id + equal to a currently active session MUST initiate a re-auth towards + the user, if the service supports this particular feature. Each + Diameter application MUST state whether server-initiated re-auth is + supported, since some applications do not allow access devices to + prompt the user for re-auth. + +8.3.1. Re-Auth-Request + + The Re-Auth-Request (RAR), indicated by the Command Code set to 258 + and the message flags' 'R' bit set, may be sent by any server to the + access device that is providing session service, to request that the + user be re-authenticated and/or re-authorized. + + + Message Format + + <RAR> ::= < Diameter Header: 258, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + { Re-Auth-Request-Type } + [ User-Name ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + +8.3.2. Re-Auth-Answer + + The Re-Auth-Answer (RAA), indicated by the Command Code set to 258 + and the message flags' 'R' bit clear, is sent in response to the RAR. + The Result-Code AVP MUST be present, and it indicates the disposition + of the request. + + + + +Fajardo, et al. Standards Track [Page 110] + +RFC 6733 Diameter Base Protocol October 2012 + + + A successful RAA message MUST be followed by an application-specific + authentication and/or authorization message. + + Message Format + + <RAA> ::= < Diameter Header: 258, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + +8.4. Session Termination + + It is necessary for a Diameter server that authorized a session, for + which it is maintaining state, to be notified when that session is no + longer active, both for tracking purposes as well as to allow + stateful agents to release any resources that they may have provided + for the user's session. For sessions whose state is not being + maintained, this section is not used. + + When a user session that required Diameter authorization terminates, + the access device that provided the service MUST issue a Session- + Termination-Request (STR) message to the Diameter server that + authorized the service, to notify it that the session is no longer + active. An STR MUST be issued when a user session terminates for any + reason, including user logoff, expiration of Session-Timeout, + administrative action, termination upon receipt of an Abort-Session- + Request (see below), orderly shutdown of the access device, etc. + + The access device also MUST issue an STR for a session that was + authorized but never actually started. This could occur, for + example, due to a sudden resource shortage in the access device, or + because the access device is unwilling to provide the type of service + requested in the authorization, or because the access device does not + support a mandatory AVP returned in the authorization, etc. + + It is also possible that a session that was authorized is never + actually started due to action of a proxy. For example, a proxy may + + + +Fajardo, et al. Standards Track [Page 111] + +RFC 6733 Diameter Base Protocol October 2012 + + + modify an authorization answer, converting the result from success to + failure, prior to forwarding the message to the access device. If + the answer did not contain an Auth-Session-State AVP with the value + NO_STATE_MAINTAINED, a proxy that causes an authorized session not to + be started MUST issue an STR to the Diameter server that authorized + the session, since the access device has no way of knowing that the + session had been authorized. + + A Diameter server that receives an STR message MUST clean up + resources (e.g., session state) associated with the Session-Id + specified in the STR and return a Session-Termination-Answer. + + A Diameter server also MUST clean up resources when the Session- + Timeout expires, or when the Authorization-Lifetime and the Auth- + Grace-Period AVPs expire without receipt of a re-authorization + request, regardless of whether an STR for that session is received. + The access device is not expected to provide service beyond the + expiration of these timers; thus, expiration of either of these + timers implies that the access device may have unexpectedly shut + down. + +8.4.1. Session-Termination-Request + + The Session-Termination-Request (STR), indicated by the Command Code + set to 275 and the Command Flags' 'R' bit set, is sent by a Diameter + client or by a Diameter proxy to inform the Diameter server that an + authenticated and/or authorized session is being terminated. + + Message Format + + <STR> ::= < Diameter Header: 275, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Application-Id } + { Termination-Cause } + [ User-Name ] + [ Destination-Host ] + * [ Class ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + + + + + + +Fajardo, et al. Standards Track [Page 112] + +RFC 6733 Diameter Base Protocol October 2012 + + +8.4.2. Session-Termination-Answer + + The Session-Termination-Answer (STA), indicated by the Command Code + set to 275 and the message flags' 'R' bit clear, is sent by the + Diameter server to acknowledge the notification that the session has + been terminated. The Result-Code AVP MUST be present, and it MAY + contain an indication that an error occurred while servicing the STR. + + Upon sending or receipt of the STA, the Diameter server MUST release + all resources for the session indicated by the Session-Id AVP. Any + intermediate server in the Proxy-Chain MAY also release any + resources, if necessary. + + Message Format + + <STA> ::= < Diameter Header: 275, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + * [ Class ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Origin-State-Id ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + +8.5. Aborting a Session + + A Diameter server may request that the access device stop providing + service for a particular session by issuing an Abort-Session-Request + (ASR). + + For example, the Diameter server that originally authorized the + session may be required to cause that session to be stopped for lack + of credit or other reasons that were not anticipated when the session + was first authorized. + + An access device that receives an ASR with Session-ID equal to a + currently active session MAY stop the session. Whether the access + device stops the session or not is implementation and/or + configuration dependent. For example, an access device may honor + ASRs from certain agents only. In any case, the access device MUST + + + +Fajardo, et al. Standards Track [Page 113] + +RFC 6733 Diameter Base Protocol October 2012 + + + respond with an Abort-Session-Answer, including a Result-Code AVP to + indicate what action it took. + +8.5.1. Abort-Session-Request + + The Abort-Session-Request (ASR), indicated by the Command Code set to + 274 and the message flags' 'R' bit set, may be sent by any Diameter + server or any Diameter proxy to the access device that is providing + session service, to request that the session identified by the + Session-Id be stopped. + + Message Format + + <ASR> ::= < Diameter Header: 274, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + [ User-Name ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + +8.5.2. Abort-Session-Answer + + The Abort-Session-Answer (ASA), indicated by the Command Code set to + 274 and the message flags' 'R' bit clear, is sent in response to the + ASR. The Result-Code AVP MUST be present and indicates the + disposition of the request. + + If the session identified by Session-Id in the ASR was successfully + terminated, the Result-Code is set to DIAMETER_SUCCESS. If the + session is not currently active, the Result-Code is set to + DIAMETER_UNKNOWN_SESSION_ID. If the access device does not stop the + session for any other reason, the Result-Code is set to + DIAMETER_UNABLE_TO_COMPLY. + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 114] + +RFC 6733 Diameter Base Protocol October 2012 + + + Message Format + + <ASA> ::= < Diameter Header: 274, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + +8.6. Inferring Session Termination from Origin-State-Id + + The Origin-State-Id is used to allow detection of terminated sessions + for which no STR would have been issued, due to unanticipated + shutdown of an access device. + + A Diameter client or access device increments the value of the + Origin-State-Id every time it is started or powered up. The new + Origin-State-Id is then sent in the CER/CEA message immediately upon + connection to the server. The Diameter server receiving the new + Origin-State-Id can determine whether the sending Diameter client had + abruptly shut down by comparing the old value of the Origin-State-Id + it has kept for that specific client is less than the new value and + whether it has un-terminated sessions originating from that client. + + An access device can also include the Origin-State-Id in request + messages other than the CER if there are relays or proxies in between + the access device and the server. In this case, however, the server + cannot discover that the access device has been restarted unless and + until it receives a new request from it. Therefore, this mechanism + is more opportunistic across proxies and relays. + + The Diameter server may assume that all sessions that were active + prior to detection of a client restart have been terminated. The + Diameter server MAY clean up all session state associated with such + lost sessions, and it MAY also issue STRs for all such lost sessions + that were authorized on upstream servers, to allow session state to + be cleaned up globally. + + + + + +Fajardo, et al. Standards Track [Page 115] + +RFC 6733 Diameter Base Protocol October 2012 + + +8.7. Auth-Request-Type AVP + + The Auth-Request-Type AVP (AVP Code 274) is of type Enumerated and is + included in application-specific auth requests to inform the peers + whether a user is to be authenticated only, authorized only, or both. + Note any value other than both MAY cause RADIUS interoperability + issues. The following values are defined: + + AUTHENTICATE_ONLY 1 + + The request being sent is for authentication only, and it MUST + contain the relevant application-specific authentication AVPs that + are needed by the Diameter server to authenticate the user. + + AUTHORIZE_ONLY 2 + + The request being sent is for authorization only, and it MUST + contain the application-specific authorization AVPs that are + necessary to identify the service being requested/offered. + + AUTHORIZE_AUTHENTICATE 3 + + The request contains a request for both authentication and + authorization. The request MUST include both the relevant + application-specific authentication information and authorization + information necessary to identify the service being requested/ + offered. + +8.8. Session-Id AVP + + The Session-Id AVP (AVP Code 263) is of type UTF8String and is used + to identify a specific session (see Section 8). All messages + pertaining to a specific session MUST include only one Session-Id + AVP, and the same value MUST be used throughout the life of a + session. When present, the Session-Id SHOULD appear immediately + following the Diameter header (see Section 3). + + The Session-Id MUST be globally and eternally unique, as it is meant + to uniquely identify a user session without reference to any other + information, and it may be needed to correlate historical + authentication information with accounting information. The + Session-Id includes a mandatory portion and an implementation-defined + portion; a recommended format for the implementation-defined portion + is outlined below. + + The Session-Id MUST begin with the sender's identity encoded in the + DiameterIdentity type (see Section 4.3.1). The remainder of the + Session-Id is delimited by a ";" character, and it MAY be any + + + +Fajardo, et al. Standards Track [Page 116] + +RFC 6733 Diameter Base Protocol October 2012 + + + sequence that the client can guarantee to be eternally unique; + however, the following format is recommended, (square brackets [] + indicate an optional element): + + <DiameterIdentity>;<high 32 bits>;<low 32 bits>[;<optional value>] + + <high 32 bits> and <low 32 bits> are decimal representations of the + high and low 32 bits of a monotonically increasing 64-bit value. The + 64-bit value is rendered in two part to simplify formatting by 32-bit + processors. At startup, the high 32 bits of the 64-bit value MAY be + initialized to the time in NTP format [RFC5905], and the low 32 bits + MAY be initialized to zero. This will for practical purposes + eliminate the possibility of overlapping Session-Ids after a reboot, + assuming the reboot process takes longer than a second. + Alternatively, an implementation MAY keep track of the increasing + value in non-volatile memory. + + + <optional value> is implementation specific, but it may include a + modem's device Id, a Layer 2 address, timestamp, etc. + + Example, in which there is no optional value: + + accesspoint7.example.com;1876543210;523 + + Example, in which there is an optional value: + + accesspoint7.example.com;1876543210;523;[email protected] + + The Session-Id is created by the Diameter application initiating the + session, which, in most cases, is done by the client. Note that a + Session-Id MAY be used for both the authentication, authorization, + and accounting commands of a given application. + +8.9. Authorization-Lifetime AVP + + The Authorization-Lifetime AVP (AVP Code 291) is of type Unsigned32 + and contains the maximum number of seconds of service to be provided + to the user before the user is to be re-authenticated and/or re- + authorized. Care should be taken when the Authorization-Lifetime + value is determined, since a low, non-zero value could create + significant Diameter traffic, which could congest both the network + and the agents. + + A value of zero (0) means that immediate re-auth is necessary by the + access device. The absence of this AVP, or a value of all ones + (meaning all bits in the 32-bit field are set to one) means no re- + auth is expected. + + + +Fajardo, et al. Standards Track [Page 117] + +RFC 6733 Diameter Base Protocol October 2012 + + + If both this AVP and the Session-Timeout AVP are present in a + message, the value of the latter MUST NOT be smaller than the + Authorization-Lifetime AVP. + + An Authorization-Lifetime AVP MAY be present in re-authorization + messages, and it contains the number of seconds the user is + authorized to receive service from the time the re-auth answer + message is received by the access device. + + This AVP MAY be provided by the client as a hint of the maximum + lifetime that it is willing to accept. The server MUST return a + value that is equal to, or smaller than, the one provided by the + client. + +8.10. Auth-Grace-Period AVP + + The Auth-Grace-Period AVP (AVP Code 276) is of type Unsigned32 and + contains the number of seconds the Diameter server will wait + following the expiration of the Authorization-Lifetime AVP before + cleaning up resources for the session. + +8.11. Auth-Session-State AVP + + The Auth-Session-State AVP (AVP Code 277) is of type Enumerated and + specifies whether state is maintained for a particular session. The + client MAY include this AVP in requests as a hint to the server, but + the value in the server's answer message is binding. The following + values are supported: + + STATE_MAINTAINED 0 + + This value is used to specify that session state is being + maintained, and the access device MUST issue a session termination + message when service to the user is terminated. This is the + default value. + + NO_STATE_MAINTAINED 1 + + This value is used to specify that no session termination messages + will be sent by the access device upon expiration of the + Authorization-Lifetime. + +8.12. Re-Auth-Request-Type AVP + + The Re-Auth-Request-Type AVP (AVP Code 285) is of type Enumerated and + is included in application-specific auth answers to inform the client + of the action expected upon expiration of the Authorization-Lifetime. + + + + +Fajardo, et al. Standards Track [Page 118] + +RFC 6733 Diameter Base Protocol October 2012 + + + If the answer message contains an Authorization-Lifetime AVP with a + positive value, the Re-Auth-Request-Type AVP MUST be present in an + answer message. The following values are defined: + + AUTHORIZE_ONLY 0 + + An authorization only re-auth is expected upon expiration of the + Authorization-Lifetime. This is the default value if the AVP is + not present in answer messages that include the Authorization- + Lifetime. + + AUTHORIZE_AUTHENTICATE 1 + + An authentication and authorization re-auth is expected upon + expiration of the Authorization-Lifetime. + +8.13. Session-Timeout AVP + + The Session-Timeout AVP (AVP Code 27) [RFC2865] is of type Unsigned32 + and contains the maximum number of seconds of service to be provided + to the user before termination of the session. When both the + Session-Timeout and the Authorization-Lifetime AVPs are present in an + answer message, the former MUST be equal to or greater than the value + of the latter. + + A session that terminates on an access device due to the expiration + of the Session-Timeout MUST cause an STR to be issued, unless both + the access device and the home server had previously agreed that no + session termination messages would be sent (see Section 8). + + A Session-Timeout AVP MAY be present in a re-authorization answer + message, and it contains the remaining number of seconds from the + beginning of the re-auth. + + A value of zero, or the absence of this AVP, means that this session + has an unlimited number of seconds before termination. + + This AVP MAY be provided by the client as a hint of the maximum + timeout that it is willing to accept. However, the server MAY return + a value that is equal to, or smaller than, the one provided by the + client. + +8.14. User-Name AVP + + The User-Name AVP (AVP Code 1) [RFC2865] is of type UTF8String, which + contains the User-Name, in a format consistent with the NAI + specification [RFC4282]. + + + + +Fajardo, et al. Standards Track [Page 119] + +RFC 6733 Diameter Base Protocol October 2012 + + +8.15. Termination-Cause AVP + + The Termination-Cause AVP (AVP Code 295) is of type Enumerated, and + is used to indicate the reason why a session was terminated on the + access device. The currently assigned values for this AVP can be + found in the IANA registry for Termination-Cause AVP Values + [IANATCV]. + +8.16. Origin-State-Id AVP + + The Origin-State-Id AVP (AVP Code 278), of type Unsigned32, is a + monotonically increasing value that is advanced whenever a Diameter + entity restarts with loss of previous state, for example, upon + reboot. Origin-State-Id MAY be included in any Diameter message, + including CER. + + A Diameter entity issuing this AVP MUST create a higher value for + this AVP each time its state is reset. A Diameter entity MAY set + Origin-State-Id to the time of startup, or it MAY use an incrementing + counter retained in non-volatile memory across restarts. + + The Origin-State-Id, if present, MUST reflect the state of the entity + indicated by Origin-Host. If a proxy modifies Origin-Host, it MUST + either remove Origin-State-Id or modify it appropriately as well. + Typically, Origin-State-Id is used by an access device that always + starts up with no active sessions; that is, any session active prior + to restart will have been lost. By including Origin-State-Id in a + message, it allows other Diameter entities to infer that sessions + associated with a lower Origin-State-Id are no longer active. If an + access device does not intend for such inferences to be made, it MUST + either not include Origin-State-Id in any message or set its value to + 0. + +8.17. Session-Binding AVP + + The Session-Binding AVP (AVP Code 270) is of type Unsigned32, and it + MAY be present in application-specific authorization answer messages. + If present, this AVP MAY inform the Diameter client that all future + application-specific re-auth and Session-Termination-Request messages + for this session MUST be sent to the same authorization server. + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 120] + +RFC 6733 Diameter Base Protocol October 2012 + + + This field is a bit mask, and the following bits have been defined: + + RE_AUTH 1 + + When set, future re-auth messages for this session MUST NOT + include the Destination-Host AVP. When cleared, the default + value, the Destination-Host AVP MUST be present in all re-auth + messages for this session. + + STR 2 + + When set, the STR message for this session MUST NOT include the + Destination-Host AVP. When cleared, the default value, the + Destination-Host AVP MUST be present in the STR message for this + session. + + ACCOUNTING 4 + + When set, all accounting messages for this session MUST NOT + include the Destination-Host AVP. When cleared, the default + value, the Destination-Host AVP, if known, MUST be present in all + accounting messages for this session. + +8.18. Session-Server-Failover AVP + + The Session-Server-Failover AVP (AVP Code 271) is of type Enumerated + and MAY be present in application-specific authorization answer + messages that either do not include the Session-Binding AVP or + include the Session-Binding AVP with any of the bits set to a zero + value. If present, this AVP MAY inform the Diameter client that if a + re-auth or STR message fails due to a delivery problem, the Diameter + client SHOULD issue a subsequent message without the Destination-Host + AVP. When absent, the default value is REFUSE_SERVICE. + + The following values are supported: + + REFUSE_SERVICE 0 + + If either the re-auth or the STR message delivery fails, terminate + service with the user and do not attempt any subsequent attempts. + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 121] + +RFC 6733 Diameter Base Protocol October 2012 + + + TRY_AGAIN 1 + + If either the re-auth or the STR message delivery fails, resend + the failed message without the Destination-Host AVP present. + + ALLOW_SERVICE 2 + + If re-auth message delivery fails, assume that re-authorization + succeeded. If STR message delivery fails, terminate the session. + + TRY_AGAIN_ALLOW_SERVICE 3 + + If either the re-auth or the STR message delivery fails, resend + the failed message without the Destination-Host AVP present. If + the second delivery fails for re-auth, assume re-authorization + succeeded. If the second delivery fails for STR, terminate the + session. + +8.19. Multi-Round-Time-Out AVP + + The Multi-Round-Time-Out AVP (AVP Code 272) is of type Unsigned32 and + SHOULD be present in application-specific authorization answer + messages whose Result-Code AVP is set to DIAMETER_MULTI_ROUND_AUTH. + This AVP contains the maximum number of seconds that the access + device MUST provide the user in responding to an authentication + request. + +8.20. Class AVP + + The Class AVP (AVP Code 25) is of type OctetString and is used by + Diameter servers to return state information to the access device. + When one or more Class AVPs are present in application-specific + authorization answer messages, they MUST be present in subsequent re- + authorization, session termination and accounting messages. Class + AVPs found in a re-authorization answer message override the ones + found in any previous authorization answer message. Diameter server + implementations SHOULD NOT return Class AVPs that require more than + 4096 bytes of storage on the Diameter client. A Diameter client that + receives Class AVPs whose size exceeds local available storage MUST + terminate the session. + +8.21. Event-Timestamp AVP + + The Event-Timestamp (AVP Code 55) is of type Time and MAY be included + in an Accounting-Request and Accounting-Answer messages to record the + time that the reported event occurred, in seconds since January 1, + 1900 00:00 UTC. + + + + +Fajardo, et al. Standards Track [Page 122] + +RFC 6733 Diameter Base Protocol October 2012 + + +9. Accounting + + This accounting protocol is based on a server directed model with + capabilities for real-time delivery of accounting information. + Several fault resilience methods [RFC2975] have been built into the + protocol in order minimize loss of accounting data in various fault + situations and under different assumptions about the capabilities of + the used devices. + +9.1. Server Directed Model + + The server directed model means that the device generating the + accounting data gets information from either the authorization server + (if contacted) or the accounting server regarding the way accounting + data shall be forwarded. This information includes accounting record + timeliness requirements. + + As discussed in [RFC2975], real-time transfer of accounting records + is a requirement, such as the need to perform credit-limit checks and + fraud detection. Note that batch accounting is not a requirement, + and is therefore not supported by Diameter. Should batched + accounting be required in the future, a new Diameter application will + need to be created, or it could be handled using another protocol. + Note, however, that even if at the Diameter layer, accounting + requests are processed one by one; transport protocols used under + Diameter typically batch several requests in the same packet under + heavy traffic conditions. This may be sufficient for many + applications. + + The authorization server (chain) directs the selection of proper + transfer strategy, based on its knowledge of the user and + relationships of roaming partnerships. The server (or agents) uses + the Acct-Interim-Interval and Accounting-Realtime-Required AVPs to + control the operation of the Diameter peer operating as a client. + The Acct-Interim-Interval AVP, when present, instructs the Diameter + node acting as a client to produce accounting records continuously + even during a session. Accounting-Realtime-Required AVP is used to + control the behavior of the client when the transfer of accounting + records from the Diameter client is delayed or unsuccessful. + + The Diameter accounting server MAY override the interim interval or + the real-time requirements by including the Acct-Interim-Interval or + Accounting-Realtime-Required AVP in the Accounting-Answer message. + When one of these AVPs is present, the latest value received SHOULD + be used in further accounting activities for the same session. + + + + + + +Fajardo, et al. Standards Track [Page 123] + +RFC 6733 Diameter Base Protocol October 2012 + + +9.2. Protocol Messages + + A Diameter node that receives a successful authentication and/or + authorization message from the Diameter server SHOULD collect + accounting information for the session. The Accounting-Request + message is used to transmit the accounting information to the + Diameter server, which MUST reply with the Accounting-Answer message + to confirm reception. The Accounting-Answer message includes the + Result-Code AVP, which MAY indicate that an error was present in the + accounting message. The value of the Accounting-Realtime-Required + AVP received earlier for the session in question may indicate that + the user's session has to be terminated when a rejected Accounting- + Request message was received. + +9.3. Accounting Application Extension and Requirements + + Each Diameter application (e.g., NASREQ, Mobile IP) SHOULD define its + service-specific AVPs that MUST be present in the Accounting-Request + message in a section titled "Accounting AVPs". The application MUST + assume that the AVPs described in this document will be present in + all Accounting messages, so only their respective service-specific + AVPs need to be defined in that section. + + Applications have the option of using one or both of the following + accounting application extension models: + + Split Accounting Service + + The accounting message will carry the Application Id of the + Diameter base accounting application (see Section 2.4). + Accounting messages may be routed to Diameter nodes other than the + corresponding Diameter application. These nodes might be + centralized accounting servers that provide accounting service for + multiple different Diameter applications. These nodes MUST + advertise the Diameter base accounting Application Id during + capabilities exchange. + + Coupled Accounting Service + + The accounting message will carry the Application Id of the + application that is using it. The application itself will process + the received accounting records or forward them to an accounting + server. There is no accounting application advertisement required + during capabilities exchange, and the accounting messages will be + routed the same way as any of the other application messages. + + In cases where an application does not define its own accounting + service, it is preferred that the split accounting model be used. + + + +Fajardo, et al. Standards Track [Page 124] + +RFC 6733 Diameter Base Protocol October 2012 + + +9.4. Fault Resilience + + Diameter base protocol mechanisms are used to overcome small message + loss and network faults of a temporary nature. + + Diameter peers acting as clients MUST implement the use of failover + to guard against server failures and certain network failures. + Diameter peers acting as agents or related off-line processing + systems MUST detect duplicate accounting records caused by the + sending of the same record to several servers and duplication of + messages in transit. This detection MUST be based on the inspection + of the Session-Id and Accounting-Record-Number AVP pairs. Appendix C + discusses duplicate detection needs and implementation issues. + + Diameter clients MAY have non-volatile memory for the safe storage of + accounting records over reboots or extended network failures, network + partitions, and server failures. If such memory is available, the + client SHOULD store new accounting records there as soon as the + records are created and until a positive acknowledgement of their + reception from the Diameter server has been received. Upon a reboot, + the client MUST start sending the records in the non-volatile memory + to the accounting server with the appropriate modifications in + termination cause, session length, and other relevant information in + the records. + + A further application of this protocol may include AVPs to control + the maximum number of accounting records that may be stored in the + Diameter client without committing them to the non-volatile memory or + transferring them to the Diameter server. + + The client SHOULD NOT remove the accounting data from any of its + memory areas before the correct Accounting-Answer has been received. + The client MAY remove the oldest, undelivered, or as yet + unacknowledged accounting data if it runs out of resources such as + memory. It is an implementation-dependent matter for the client to + accept new sessions under this condition. + +9.5. Accounting Records + + In all accounting records, the Session-Id AVP MUST be present; the + User-Name AVP MUST be present if it is available to the Diameter + client. + + Different types of accounting records are sent depending on the + actual type of accounted service and the authorization server's + directions for interim accounting. If the accounted service is a + + + + + +Fajardo, et al. Standards Track [Page 125] + +RFC 6733 Diameter Base Protocol October 2012 + + + one-time event, meaning that the start and stop of the event are + simultaneous, then the Accounting-Record-Type AVP MUST be present and + set to the value EVENT_RECORD. + + If the accounted service is of a measurable length, then the AVP MUST + use the values START_RECORD, STOP_RECORD, and possibly, + INTERIM_RECORD. If the authorization server has not directed interim + accounting to be enabled for the session, two accounting records MUST + be generated for each service of type session. When the initial + Accounting-Request for a given session is sent, the Accounting- + Record-Type AVP MUST be set to the value START_RECORD. When the last + Accounting-Request is sent, the value MUST be STOP_RECORD. + + If the authorization server has directed interim accounting to be + enabled, the Diameter client MUST produce additional records between + the START_RECORD and STOP_RECORD, marked INTERIM_RECORD. The + production of these records is directed by Acct-Interim-Interval as + well as any re-authentication or re-authorization of the session. + The Diameter client MUST overwrite any previous interim accounting + records that are locally stored for delivery, if a new record is + being generated for the same session. This ensures that only one + pending interim record can exist on an access device for any given + session. + + A particular value of Accounting-Sub-Session-Id MUST appear only in + one sequence of accounting records from a Diameter client, except for + the purposes of retransmission. The one sequence that is sent MUST + be either one record with Accounting-Record-Type AVP set to the value + EVENT_RECORD or several records starting with one having the value + START_RECORD, followed by zero or more INTERIM_RECORDs and a single + STOP_RECORD. A particular Diameter application specification MUST + define the type of sequences that MUST be used. + +9.6. Correlation of Accounting Records + + If an application uses accounting messages, it can correlate + accounting records with a specific application session by using the + Session-Id of the particular application session in the accounting + messages. Accounting messages MAY also use a different Session-Id + from that of the application sessions, in which case, other session- + related information is needed to perform correlation. + + In cases where an application requires multiple accounting sub- + sessions, an Accounting-Sub-Session-Id AVP is used to differentiate + each sub-session. The Session-Id would remain constant for all sub- + sessions and is used to correlate all the sub-sessions to a + particular application session. Note that receiving a STOP_RECORD + + + + +Fajardo, et al. Standards Track [Page 126] + +RFC 6733 Diameter Base Protocol October 2012 + + + with no Accounting-Sub-Session-Id AVP when sub-sessions were + originally used in the START_RECORD messages implies that all sub- + sessions are terminated. + + There are also cases where an application needs to correlate multiple + application sessions into a single accounting record; the accounting + record may span multiple different Diameter applications and sessions + used by the same user at a given time. In such cases, the Acct- + Multi-Session-Id AVP is used. The Acct-Multi-Session-Id AVP SHOULD + be signaled by the server to the access device (typically, during + authorization) when it determines that a request belongs to an + existing session. The access device MUST then include the Acct- + Multi-Session-Id AVP in all subsequent accounting messages. + + The Acct-Multi-Session-Id AVP MAY include the value of the original + Session-Id. Its contents are implementation specific, but the MUST + be globally unique across other Acct-Multi-Session-Ids and MUST NOT + change during the life of a session. + + A Diameter application document MUST define the exact concept of a + session that is being accounted, and it MAY define the concept of a + multi-session. For instance, the NASREQ DIAMETER application treats + a single PPP connection to a Network Access Server as one session and + a set of Multilink PPP sessions as one multi-session. + +9.7. Accounting Command Codes + + This section defines Command Code values that MUST be supported by + all Diameter implementations that provide accounting services. + +9.7.1. Accounting-Request + + The Accounting-Request (ACR) command, indicated by the Command Code + field set to 271 and the Command Flags' 'R' bit set, is sent by a + Diameter node, acting as a client, in order to exchange accounting + information with a peer. + + In addition to the AVPs listed below, Accounting-Request messages + SHOULD include service-specific accounting AVPs. + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 127] + +RFC 6733 Diameter Base Protocol October 2012 + + + Message Format + + <ACR> ::= < Diameter Header: 271, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Destination-Host ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + +9.7.2. Accounting-Answer + + The Accounting-Answer (ACA) command, indicated by the Command Code + field set to 271 and the Command Flags' 'R' bit cleared, is used to + acknowledge an Accounting-Request command. The Accounting-Answer + command contains the same Session-Id as the corresponding request. + + Only the target Diameter server, known as the home Diameter server, + SHOULD respond with the Accounting-Answer command. + + In addition to the AVPs listed below, Accounting-Answer messages + SHOULD include service-specific accounting AVPs. + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 128] + +RFC 6733 Diameter Base Protocol October 2012 + + + Message Format + + <ACA> ::= < Diameter Header: 271, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ AVP ] + +9.8. Accounting AVPs + + This section contains AVPs that describe accounting usage information + related to a specific session. + +9.8.1. Accounting-Record-Type AVP + + The Accounting-Record-Type AVP (AVP Code 480) is of type Enumerated + and contains the type of accounting record being sent. The following + values are currently defined for the Accounting-Record-Type AVP: + + EVENT_RECORD 1 + + An Accounting Event Record is used to indicate that a one-time + event has occurred (meaning that the start and end of the event + are simultaneous). This record contains all information relevant + to the service, and it is the only record of the service. + + + + + + + + + +Fajardo, et al. Standards Track [Page 129] + +RFC 6733 Diameter Base Protocol October 2012 + + + START_RECORD 2 + + Accounting Start, Interim, and Stop Records are used to indicate + that a service of a measurable length has been given. An + Accounting Start Record is used to initiate an accounting session + and contains accounting information that is relevant to the + initiation of the session. + + INTERIM_RECORD 3 + + An Interim Accounting Record contains cumulative accounting + information for an existing accounting session. Interim + Accounting Records SHOULD be sent every time a re-authentication + or re-authorization occurs. Further, additional interim record + triggers MAY be defined by application-specific Diameter + applications. The selection of whether to use INTERIM_RECORD + records is done by the Acct-Interim-Interval AVP. + + STOP_RECORD 4 + + An Accounting Stop Record is sent to terminate an accounting + session and contains cumulative accounting information relevant to + the existing session. + +9.8.2. Acct-Interim-Interval AVP + + The Acct-Interim-Interval AVP (AVP Code 85) is of type Unsigned32 and + is sent from the Diameter home authorization server to the Diameter + client. The client uses information in this AVP to decide how and + when to produce accounting records. With different values in this + AVP, service sessions can result in one, two, or two+N accounting + records, based on the needs of the home organization. The following + accounting record production behavior is directed by the inclusion of + this AVP: + + 1. The omission of the Acct-Interim-Interval AVP or its inclusion + with Value field set to 0 means that EVENT_RECORD, START_RECORD, + and STOP_RECORD are produced, as appropriate for the service. + + 2. The inclusion of the AVP with Value field set to a non-zero value + means that INTERIM_RECORD records MUST be produced between the + START_RECORD and STOP_RECORD records. The Value field of this + AVP is the nominal interval between these records in seconds. + The Diameter node that originates the accounting information, + known as the client, MUST produce the first INTERIM_RECORD record + roughly at the time when this nominal interval has elapsed from + + + + + +Fajardo, et al. Standards Track [Page 130] + +RFC 6733 Diameter Base Protocol October 2012 + + + the START_RECORD, the next one again as the interval has elapsed + once more, and so on until the session ends and a STOP_RECORD + record is produced. + + The client MUST ensure that the interim record production times + are randomized so that large accounting message storms are not + created either among records or around a common service start + time. + +9.8.3. Accounting-Record-Number AVP + + The Accounting-Record-Number AVP (AVP Code 485) is of type Unsigned32 + and identifies this record within one session. As Session-Id AVPs + are globally unique, the combination of Session-Id and Accounting- + Record-Number AVPs is also globally unique and can be used in + matching accounting records with confirmations. An easy way to + produce unique numbers is to set the value to 0 for records of type + EVENT_RECORD and START_RECORD and set the value to 1 for the first + INTERIM_RECORD, 2 for the second, and so on until the value for + STOP_RECORD is one more than for the last INTERIM_RECORD. + +9.8.4. Acct-Session-Id AVP + + The Acct-Session-Id AVP (AVP Code 44) is of type OctetString is only + used when RADIUS/Diameter translation occurs. This AVP contains the + contents of the RADIUS Acct-Session-Id attribute. + +9.8.5. Acct-Multi-Session-Id AVP + + The Acct-Multi-Session-Id AVP (AVP Code 50) is of type UTF8String, + following the format specified in Section 8.8. The Acct-Multi- + Session-Id AVP is used to link multiple related accounting sessions, + where each session would have a unique Session-Id but the same Acct- + Multi-Session-Id AVP. This AVP MAY be returned by the Diameter + server in an authorization answer, and it MUST be used in all + accounting messages for the given session. + +9.8.6. Accounting-Sub-Session-Id AVP + + The Accounting-Sub-Session-Id AVP (AVP Code 287) is of type + Unsigned64 and contains the accounting sub-session identifier. The + combination of the Session-Id and this AVP MUST be unique per sub- + session, and the value of this AVP MUST be monotonically increased by + one for all new sub-sessions. The absence of this AVP implies no + sub-sessions are in use, with the exception of an Accounting-Request + whose Accounting-Record-Type is set to STOP_RECORD. A STOP_RECORD + message with no Accounting-Sub-Session-Id AVP present will signal the + termination of all sub-sessions for a given Session-Id. + + + +Fajardo, et al. Standards Track [Page 131] + +RFC 6733 Diameter Base Protocol October 2012 + + +9.8.7. Accounting-Realtime-Required AVP + + The Accounting-Realtime-Required AVP (AVP Code 483) is of type + Enumerated and is sent from the Diameter home authorization server to + the Diameter client or in the Accounting-Answer from the accounting + server. The client uses information in this AVP to decide what to do + if the sending of accounting records to the accounting server has + been temporarily prevented due to, for instance, a network problem. + + DELIVER_AND_GRANT 1 + + The AVP with Value field set to DELIVER_AND_GRANT means that the + service MUST only be granted as long as there is a connection to + an accounting server. Note that the set of alternative accounting + servers are treated as one server in this sense. Having to move + the accounting record stream to a backup server is not a reason to + discontinue the service to the user. + + GRANT_AND_STORE 2 + + The AVP with Value field set to GRANT_AND_STORE means that service + SHOULD be granted if there is a connection, or as long as records + can still be stored as described in Section 9.4. + + This is the default behavior if the AVP isn't included in the + reply from the authorization server. + + GRANT_AND_LOSE 3 + + The AVP with Value field set to GRANT_AND_LOSE means that service + SHOULD be granted even if the records cannot be delivered or + stored. + +10. AVP Occurrence Tables + + The following tables present the AVPs defined in this document and + specify in which Diameter messages they MAY or MAY NOT be present. + AVPs that occur only inside a Grouped AVP are not shown in these + tables. + + The tables use the following symbols: + + 0 The AVP MUST NOT be present in the message. + + 0+ Zero or more instances of the AVP MAY be present in the + message. + + + + + +Fajardo, et al. Standards Track [Page 132] + +RFC 6733 Diameter Base Protocol October 2012 + + + 0-1 Zero or one instance of the AVP MAY be present in the message. + It is considered an error if there are more than one instance + of the AVP. + + 1 One instance of the AVP MUST be present in the message. + + 1+ At least one instance of the AVP MUST be present in the + message. + +10.1. Base Protocol Command AVP Table + + The table in this section is limited to the non-Accounting Command + Codes defined in this specification. + + +-----------------------------------------------+ + | Command Code | + +---+---+---+---+---+---+---+---+---+---+---+---+ + Attribute Name |CER|CEA|DPR|DPA|DWR|DWA|RAR|RAA|ASR|ASA|STR|STA| + --------------------+---+---+---+---+---+---+---+---+---+---+---+---+ + Acct-Interim- |0 |0 |0 |0 |0 |0 |0-1|0 |0 |0 |0 |0 | + Interval | | | | | | | | | | | | | + Accounting-Realtime-|0 |0 |0 |0 |0 |0 |0-1|0 |0 |0 |0 |0 | + Required | | | | | | | | | | | | | + Acct-Application-Id |0+ |0+ |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Auth-Application-Id |0+ |0+ |0 |0 |0 |0 |1 |0 |1 |0 |1 |0 | + Auth-Grace-Period |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Auth-Request-Type |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Auth-Session-State |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Authorization- |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Lifetime | | | | | | | | | | | | | + Class |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0+ |0+ | + Destination-Host |0 |0 |0 |0 |0 |0 |1 |0 |1 |0 |0-1|0 | + Destination-Realm |0 |0 |0 |0 |0 |0 |1 |0 |1 |0 |1 |0 | + Disconnect-Cause |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Error-Message |0 |0-1|0 |0-1|0 |0-1|0 |0-1|0 |0-1|0 |0-1| + Error-Reporting-Host|0 |0 |0 |0 |0 |0 |0 |0-1|0 |0-1|0 |0-1| + Failed-AVP |0 |0-1|0 |0-1|0 |0-1|0 |0-1|0 |0-1|0 |0-1| + Firmware-Revision |0-1|0-1|0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Host-IP-Address |1+ |1+ |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Inband-Security-Id |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Multi-Round-Time-Out|0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + + + + + + + + + + +Fajardo, et al. Standards Track [Page 133] + +RFC 6733 Diameter Base Protocol October 2012 + + + Origin-Host |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 | + Origin-Realm |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 |1 | + Origin-State-Id |0-1|0-1|0 |0 |0-1|0-1|0-1|0-1|0-1|0-1|0-1|0-1| + Product-Name |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Proxy-Info |0 |0 |0 |0 |0 |0 |0+ |0+ |0+ |0+ |0+ |0+ | + Redirect-Host |0 |0 |0 |0 |0 |0 |0 |0+ |0 |0+ |0 |0+ | + Redirect-Host-Usage |0 |0 |0 |0 |0 |0 |0 |0-1|0 |0-1|0 |0-1| + Redirect-Max-Cache- |0 |0 |0 |0 |0 |0 |0 |0-1|0 |0-1|0 |0-1| + Time | | | | | | | | | | | | | + Result-Code |0 |1 |0 |1 |0 |1 |0 |1 |0 |1 |0 |1 | + Re-Auth-Request-Type|0 |0 |0 |0 |0 |0 |1 |0 |0 |0 |0 |0 | + Route-Record |0 |0 |0 |0 |0 |0 |0+ |0 |0+ |0 |0+ |0 | + Session-Binding |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Session-Id |0 |0 |0 |0 |0 |0 |1 |1 |1 |1 |1 |1 | + Session-Server- |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Failover | | | | | | | | | | | | | + Session-Timeout |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Supported-Vendor-Id |0+ |0+ |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Termination-Cause |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |1 |0 | + User-Name |0 |0 |0 |0 |0 |0 |0-1|0-1|0-1|0-1|0-1|0-1| + Vendor-Id |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Vendor-Specific- |0+ |0+ |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | + Application-Id | | | | | | | | | | | | | + --------------------+---+---+---+---+---+---+---+---+---+---+---+---+ + +10.2. Accounting AVP Table + + The table in this section is used to represent which AVPs defined in + this document are to be present in the Accounting messages. These + AVP occurrence requirements are guidelines, which may be expanded, + and/or overridden by application-specific requirements in the + Diameter applications documents. + + + + + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 134] + +RFC 6733 Diameter Base Protocol October 2012 + + + +-----------+ + | Command | + | Code | + +-----+-----+ + Attribute Name | ACR | ACA | + ------------------------------+-----+-----+ + Acct-Interim-Interval | 0-1 | 0-1 | + Acct-Multi-Session-Id | 0-1 | 0-1 | + Accounting-Record-Number | 1 | 1 | + Accounting-Record-Type | 1 | 1 | + Acct-Session-Id | 0-1 | 0-1 | + Accounting-Sub-Session-Id | 0-1 | 0-1 | + Accounting-Realtime-Required | 0-1 | 0-1 | + Acct-Application-Id | 0-1 | 0-1 | + Auth-Application-Id | 0 | 0 | + Class | 0+ | 0+ | + Destination-Host | 0-1 | 0 | + Destination-Realm | 1 | 0 | + Error-Reporting-Host | 0 | 0+ | + Event-Timestamp | 0-1 | 0-1 | + Failed-AVP | 0 | 0-1 | + Origin-Host | 1 | 1 | + Origin-Realm | 1 | 1 | + Proxy-Info | 0+ | 0+ | + Route-Record | 0+ | 0 | + Result-Code | 0 | 1 | + Session-Id | 1 | 1 | + Termination-Cause | 0 | 0 | + User-Name | 0-1 | 0-1 | + Vendor-Specific-Application-Id| 0-1 | 0-1 | + ------------------------------+-----+-----+ + +11. IANA Considerations + + This section provides guidance to the Internet Assigned Numbers + Authority (IANA) regarding registration of values related to the + Diameter protocol, in accordance with [RFC5226]. Existing IANA + registries and assignments put in place by RFC 3588 remain the same + unless explicitly updated or deprecated in this section. + +11.1. AVP Header + + As defined in Section 4, the AVP header contains three fields that + require IANA namespace management: the AVP Code, Vendor-ID, and Flags + fields. + + + + + + +Fajardo, et al. Standards Track [Page 135] + +RFC 6733 Diameter Base Protocol October 2012 + + +11.1.1. AVP Codes + + There are multiple namespaces. Vendors can have their own AVP Codes + namespace that will be identified by their Vendor-ID (also known as + Enterprise-Number), and they control the assignments of their vendor- + specific AVP Codes within their own namespace. The absence of a + Vendor-ID or a Vendor-ID value of zero (0) identifies the IETF AVP + Codes namespace, which is under IANA control. The AVP Codes and + sometimes possible values in an AVP are controlled and maintained by + IANA. AVP Code 0 is not used. AVP Codes 1-255 are managed + separately as RADIUS Attribute Types. Where a Vendor-Specific AVP is + implemented by more than one vendor, allocation of global AVPs should + be encouraged instead. + + AVPs may be allocated following Expert Review (by a Designated + Expert) with Specification Required [RFC5226]. A block allocation + (release of more than three AVPs at a time for a given purpose) + requires IETF Review [RFC5226]. + +11.1.2. AVP Flags + + Section 4.1 describes the existing AVP Flags. The remaining bits can + only be assigned via a Standards Action [RFC5226]. + +11.2. Diameter Header + +11.2.1. Command Codes + + For the Diameter header, the Command Code namespace allocation has + changed. The new allocation rules are as follows: + + The Command Code values 256 - 8,388,607 (0x100 to 0x7fffff) are + for permanent, standard commands, allocated by IETF Review + [RFC5226]. + + The values 8,388,608 - 16,777,213 (0x800000 - 0xfffffd) are + reserved for vendor-specific Command Codes, to be allocated on a + First Come, First Served basis by IANA [RFC5226]. The request to + IANA for a Vendor-Specific Command Code SHOULD include a reference + to a publicly available specification that documents the command + in sufficient detail to aid in interoperability between + independent implementations. If the specification cannot be made + publicly available, the request for a vendor-specific Command Code + MUST include the contact information of persons and/or entities + responsible for authoring and maintaining the command. + + + + + + +Fajardo, et al. Standards Track [Page 136] + +RFC 6733 Diameter Base Protocol October 2012 + + + The values 16,777,214 and 16,777,215 (hexadecimal values 0xfffffe + - 0xffffff) are reserved for experimental commands. As these + codes are only for experimental and testing purposes, no guarantee + is made for interoperability between Diameter peers using + experimental commands. + +11.2.2. Command Flags + + Section 3 describes the existing Command Flags field. The remaining + bits can only be assigned via a Standards Action [RFC5226]. + +11.3. AVP Values + + For AVP values, the Experimental-Result-Code AVP value allocation has + been added; see Section 11.3.1. The old AVP value allocation rule, + IETF Consensus, has been updated to IETF Review as per [RFC5226], and + affected AVPs are listed as reminders. + +11.3.1. Experimental-Result-Code AVP + + Values for this AVP are purely local to the indicated vendor, and no + IANA registry is maintained for them. + +11.3.2. Result-Code AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.3. Accounting-Record-Type AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.4. Termination-Cause AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.5. Redirect-Host-Usage AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.6. Session-Server-Failover AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.7. Session-Binding AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + + + + + +Fajardo, et al. Standards Track [Page 137] + +RFC 6733 Diameter Base Protocol October 2012 + + +11.3.8. Disconnect-Cause AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.9. Auth-Request-Type AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.10. Auth-Session-State AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.11. Re-Auth-Request-Type AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.12. Accounting-Realtime-Required AVP Values + + New values are available for assignment via IETF Review [RFC5226]. + +11.3.13. Inband-Security-Id AVP (code 299) + + The use of this AVP has been deprecated. + +11.4. _diameters Service Name and Port Number Registration + + IANA has registered the "_diameters" service name and assigned port + numbers for TLS/TCP and DTLS/SCTP according to the guidelines given + in [RFC6335]. + + Service Name: _diameters + + Transport Protocols: TCP, SCTP + + Assignee: IESG <[email protected]> + + Contact: IETF Chair <[email protected]> + + Description: Diameter over TLS/TCP and DTLS/SCTP + + Reference: RFC 6733 + + Port Number: 5868, from the User Range + + + + + + + + +Fajardo, et al. Standards Track [Page 138] + +RFC 6733 Diameter Base Protocol October 2012 + + +11.5. SCTP Payload Protocol Identifiers + + Two SCTP payload protocol identifiers have been registered in the + SCTP Payload Protocol Identifiers registry: + + + Value | SCTP Payload Protocol Identifier + -------|----------------------------------- + 46 | Diameter in a SCTP DATA chunk + 47 | Diameter in a DTLS/SCTP DATA chunk + + +11.6. S-NAPTR Parameters + + The following tag has been registered in the S-NAPTR Application + Protocol Tags registry: + + Tag | Protocol + -------------------|--------- + diameter.dtls.sctp | DTLS/SCTP + +12. Diameter Protocol-Related Configurable Parameters + + This section contains the configurable parameters that are found + throughout this document: + + Diameter Peer + + A Diameter entity MAY communicate with peers that are statically + configured. A statically configured Diameter peer would require + that either the IP address or the fully qualified domain name + (FQDN) be supplied, which would then be used to resolve through + DNS. + + Routing Table + + A Diameter proxy server routes messages based on the realm portion + of a Network Access Identifier (NAI). The server MUST have a + table of Realm Names, and the address of the peer to which the + message must be forwarded. The routing table MAY also include a + "default route", which is typically used for all messages that + cannot be locally processed. + + Tc timer + + The Tc timer controls the frequency that transport connection + attempts are done to a peer with whom no active transport + connection exists. The recommended value is 30 seconds. + + + +Fajardo, et al. Standards Track [Page 139] + +RFC 6733 Diameter Base Protocol October 2012 + + +13. Security Considerations + + The Diameter base protocol messages SHOULD be secured by using TLS + [RFC5246] or DTLS/SCTP [RFC6083]. Additional security mechanisms + such as IPsec [RFC4301] MAY also be deployed to secure connections + between peers. However, all Diameter base protocol implementations + MUST support the use of TLS/TCP and DTLS/SCTP, and the Diameter + protocol MUST NOT be used without one of TLS, DTLS, or IPsec. + + If a Diameter connection is to be protected via TLS/TCP and DTLS/SCTP + or IPsec, then TLS/TCP and DTLS/SCTP or IPsec/IKE SHOULD begin prior + to any Diameter message exchange. All security parameters for TLS/ + TCP and DTLS/SCTP or IPsec are configured independent of the Diameter + protocol. All Diameter messages will be sent through the TLS/TCP and + DTLS/SCTP or IPsec connection after a successful setup. + + For TLS/TCP and DTLS/SCTP connections to be established in the open + state, the CER/CEA exchange MUST include an Inband-Security-ID AVP + with a value of TLS/TCP and DTLS/SCTP. The TLS/TCP and DTLS/SCTP + handshake will begin when both ends successfully reach the open + state, after completion of the CER/CEA exchange. If the TLS/TCP and + DTLS/SCTP handshake is successful, all further messages will be sent + via TLS/TCP and DTLS/SCTP. If the handshake fails, both ends MUST + move to the closed state. See Section 13.1 for more details. + +13.1. TLS/TCP and DTLS/SCTP Usage + + Diameter nodes using TLS/TCP and DTLS/SCTP for security MUST mutually + authenticate as part of TLS/TCP and DTLS/SCTP session establishment. + In order to ensure mutual authentication, the Diameter node acting as + the TLS/TCP and DTLS/SCTP server MUST request a certificate from the + Diameter node acting as TLS/TCP and DTLS/SCTP client, and the + Diameter node acting as the TLS/TCP and DTLS/SCTP client MUST be + prepared to supply a certificate on request. + + Diameter nodes MUST be able to negotiate the following TLS/TCP and + DTLS/SCTP cipher suites: + + TLS_RSA_WITH_RC4_128_MD5 + TLS_RSA_WITH_RC4_128_SHA + TLS_RSA_WITH_3DES_EDE_CBC_SHA + + Diameter nodes SHOULD be able to negotiate the following TLS/TCP and + DTLS/SCTP cipher suite: + + TLS_RSA_WITH_AES_128_CBC_SHA + + + + + +Fajardo, et al. Standards Track [Page 140] + +RFC 6733 Diameter Base Protocol October 2012 + + + Note that it is quite possible that support for the + TLS_RSA_WITH_AES_128_CBC_SHA cipher suite will be REQUIRED at some + future date. Diameter nodes MAY negotiate other TLS/TCP and DTLS/ + SCTP cipher suites. + + If public key certificates are used for Diameter security (for + example, with TLS), the value of the expiration times in the routing + and peer tables MUST NOT be greater than the expiry time in the + relevant certificates. + +13.2. Peer-to-Peer Considerations + + As with any peer-to-peer protocol, proper configuration of the trust + model within a Diameter peer is essential to security. When + certificates are used, it is necessary to configure the root + certificate authorities trusted by the Diameter peer. These root CAs + are likely to be unique to Diameter usage and distinct from the root + CAs that might be trusted for other purposes such as Web browsing. + In general, it is expected that those root CAs will be configured so + as to reflect the business relationships between the organization + hosting the Diameter peer and other organizations. As a result, a + Diameter peer will typically not be configured to allow connectivity + with any arbitrary peer. With certificate authentication, Diameter + peers may not be known beforehand and therefore peer discovery may be + required. + +13.3. AVP Considerations + + Diameter AVPs often contain security-sensitive data; for example, + user passwords and location data, network addresses and cryptographic + keys. The following AVPs defined in this document are considered to + be security-sensitive: + + o Acct-Interim-Interval + + o Accounting-Realtime-Required + + o Acct-Multi-Session-Id + + o Accounting-Record-Number + + o Accounting-Record-Type + + o Accounting-Session-Id + + o Accounting-Sub-Session-Id + + o Class + + + +Fajardo, et al. Standards Track [Page 141] + +RFC 6733 Diameter Base Protocol October 2012 + + + o Session-Id + + o Session-Binding + + o Session-Server-Failover + + o User-Name + + Diameter messages containing these or any other AVPs considered to be + security-sensitive MUST only be sent protected via mutually + authenticated TLS or IPsec. In addition, those messages MUST NOT be + sent via intermediate nodes unless there is end-to-end security + between the originator and recipient or the originator has locally + trusted configuration that indicates that end-to-end security is not + needed. For example, end-to-end security may not be required in the + case where an intermediary node is known to be operated as part of + the same administrative domain as the endpoints so that an ability to + successfully compromise the intermediary would imply a high + probability of being able to compromise the endpoints as well. Note + that no end-to-end security mechanism is specified in this document. + +14. References + +14.1. Normative References + + [FLOATPOINT] + Institute of Electrical and Electronics Engineers, "IEEE + Standard for Binary Floating-Point Arithmetic, ANSI/IEEE + Standard 754-1985", August 1985. + + [IANAADFAM] + IANA, "Address Family Numbers", + <http://www.iana.org/assignments/address-family-numbers>. + + [RFC0791] Postel, J., "Internet Protocol", STD 5, RFC 791, + September 1981. + + [RFC0793] Postel, J., "Transmission Control Protocol", STD 7, + RFC 793, September 1981. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC3492] Costello, A., "Punycode: A Bootstring encoding of Unicode + for Internationalized Domain Names in Applications + (IDNA)", RFC 3492, March 2003. + + + + + +Fajardo, et al. Standards Track [Page 142] + +RFC 6733 Diameter Base Protocol October 2012 + + + [RFC3539] Aboba, B. and J. Wood, "Authentication, Authorization and + Accounting (AAA) Transport Profile", RFC 3539, June 2003. + + [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [RFC3958] Daigle, L. and A. Newton, "Domain-Based Application + Service Location Using SRV RRs and the Dynamic Delegation + Discovery Service (DDDS)", RFC 3958, January 2005. + + [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform + Resource Identifier (URI): Generic Syntax", STD 66, + RFC 3986, January 2005. + + [RFC4004] Calhoun, P., Johansson, T., Perkins, C., Hiller, T., and + P. McCann, "Diameter Mobile IPv4 Application", RFC 4004, + August 2005. + + [RFC4005] Calhoun, P., Zorn, G., Spence, D., and D. Mitton, + "Diameter Network Access Server Application", RFC 4005, + August 2005. + + [RFC4006] Hakala, H., Mattila, L., Koskinen, J-P., Stura, M., and J. + Loughney, "Diameter Credit-Control Application", RFC 4006, + August 2005. + + [RFC4086] Eastlake, D., Schiller, J., and S. Crocker, "Randomness + Requirements for Security", BCP 106, RFC 4086, June 2005. + + [RFC4282] Aboba, B., Beadles, M., Arkko, J., and P. Eronen, "The + Network Access Identifier", RFC 4282, December 2005. + + [RFC4291] Hinden, R. and S. Deering, "IP Version 6 Addressing + Architecture", RFC 4291, February 2006. + + [RFC4960] Stewart, R., "Stream Control Transmission Protocol", + RFC 4960, September 2007. + + [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an + IANA Considerations Section in RFCs", BCP 26, RFC 5226, + May 2008. + + [RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", STD 68, RFC 5234, January 2008. + + [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security + (TLS) Protocol Version 1.2", RFC 5246, August 2008. + + + + +Fajardo, et al. Standards Track [Page 143] + +RFC 6733 Diameter Base Protocol October 2012 + + + [RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., + Housley, R., and W. Polk, "Internet X.509 Public Key + Infrastructure Certificate and Certificate Revocation List + (CRL) Profile", RFC 5280, May 2008. + + [RFC5729] Korhonen, J., Jones, M., Morand, L., and T. Tsou, + "Clarifications on the Routing of Diameter Requests Based + on the Username and the Realm", RFC 5729, December 2009. + + [RFC5890] Klensin, J., "Internationalized Domain Names for + Applications (IDNA): Definitions and Document Framework", + RFC 5890, August 2010. + + [RFC5891] Klensin, J., "Internationalized Domain Names in + Applications (IDNA): Protocol", RFC 5891, August 2010. + + [RFC6083] Tuexen, M., Seggelmann, R., and E. Rescorla, "Datagram + Transport Layer Security (DTLS) for Stream Control + Transmission Protocol (SCTP)", RFC 6083, January 2011. + + [RFC6347] Rescorla, E. and N. Modadugu, "Datagram Transport Layer + Security Version 1.2", RFC 6347, January 2012. + + [RFC6408] Jones, M., Korhonen, J., and L. Morand, "Diameter + Straightforward-Naming Authority Pointer (S-NAPTR) Usage", + RFC 6408, November 2011. + +14.2. Informative References + + [ENTERPRISE] IANA, "SMI Network Management Private Enterprise + Codes", + <http://www.iana.org/assignments/enterprise-numbers>. + + [IANATCV] IANA, "Termination-Cause AVP Values (code 295)", + <http://www.iana.org/assignments/aaa-parameters/ + aaa-parameters.xml#aaa-parameters-16>. + + [RFC1492] Finseth, C., "An Access Control Protocol, Sometimes + Called TACACS", RFC 1492, July 1993. + + [RFC1661] Simpson, W., "The Point-to-Point Protocol (PPP)", + STD 51, RFC 1661, July 1994. + + [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: + Keyed-Hashing for Message Authentication", RFC 2104, + February 1997. + + + + + +Fajardo, et al. Standards Track [Page 144] + +RFC 6733 Diameter Base Protocol October 2012 + + + [RFC2782] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR + for specifying the location of services (DNS SRV)", + RFC 2782, February 2000. + + [RFC2865] Rigney, C., Willens, S., Rubens, A., and W. Simpson, + "Remote Authentication Dial In User Service (RADIUS)", + RFC 2865, June 2000. + + [RFC2866] Rigney, C., "RADIUS Accounting", RFC 2866, June 2000. + + [RFC2869] Rigney, C., Willats, W., and P. Calhoun, "RADIUS + Extensions", RFC 2869, June 2000. + + [RFC2881] Mitton, D. and M. Beadles, "Network Access Server + Requirements Next Generation (NASREQNG) NAS Model", + RFC 2881, July 2000. + + [RFC2975] Aboba, B., Arkko, J., and D. Harrington, "Introduction + to Accounting Management", RFC 2975, October 2000. + + [RFC2989] Aboba, B., Calhoun, P., Glass, S., Hiller, T., McCann, + P., Shiino, H., Walsh, P., Zorn, G., Dommety, G., + Perkins, C., Patil, B., Mitton, D., Manning, S., + Beadles, M., Chen, X., Sivalingham, S., Hameed, A., + Munson, M., Jacobs, S., Lim, B., Hirschman, B., Hsu, + R., Koo, H., Lipford, M., Campbell, E., Xu, Y., Baba, + S., and E. Jaques, "Criteria for Evaluating AAA + Protocols for Network Access", RFC 2989, November 2000. + + [RFC3162] Aboba, B., Zorn, G., and D. Mitton, "RADIUS and IPv6", + RFC 3162, August 2001. + + [RFC3748] Aboba, B., Blunk, L., Vollbrecht, J., Carlson, J., and + H. Levkowetz, "Extensible Authentication Protocol + (EAP)", RFC 3748, June 2004. + + [RFC4301] Kent, S. and K. Seo, "Security Architecture for the + Internet Protocol", RFC 4301, December 2005. + + [RFC4690] Klensin, J., Faltstrom, P., Karp, C., and IAB, "Review + and Recommendations for Internationalized Domain Names + (IDNs)", RFC 4690, September 2006. + + [RFC5176] Chiba, M., Dommety, G., Eklund, M., Mitton, D., and B. + Aboba, "Dynamic Authorization Extensions to Remote + Authentication Dial In User Service (RADIUS)", + RFC 5176, January 2008. + + + + +Fajardo, et al. Standards Track [Page 145] + +RFC 6733 Diameter Base Protocol October 2012 + + + [RFC5461] Gont, F., "TCP's Reaction to Soft Errors", RFC 5461, + February 2009. + + [RFC5905] Mills, D., Martin, J., Burbank, J., and W. Kasch, + "Network Time Protocol Version 4: Protocol and + Algorithms Specification", RFC 5905, June 2010. + + [RFC5927] Gont, F., "ICMP Attacks against TCP", RFC 5927, + July 2010. + + [RFC6335] Cotton, M., Eggert, L., Touch, J., Westerlund, M., and + S. Cheshire, "Internet Assigned Numbers Authority + (IANA) Procedures for the Management of the Service + Name and Transport Protocol Port Number Registry", + BCP 165, RFC 6335, August 2011. + + [RFC6737] Kang, J. and G. Zorn, "The Diameter Capabilities Update + Application", RFC 6737, October 2012. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fajardo, et al. Standards Track [Page 146] + +RFC 6733 Diameter Base Protocol October 2012 + + +Appendix A. Acknowledgements + +A.1. This Document + + The authors would like to thank the following people that have + provided proposals and contributions to this document: + + To Vishnu Ram and Satendra Gera for their contributions on + capabilities updates, predictive loop avoidance, as well as many + other technical proposals. To Tolga Asveren for his insights and + contributions on almost all of the proposed solutions incorporated + into this document. To Timothy Smith for helping on the capabilities + Update and other topics. To Tony Zhang for providing fixes to + loopholes on composing Failed-AVPs as well as many other issues and + topics. To Jan Nordqvist for clearly stating the usage of + Application Ids. To Anders Kristensen for providing needed technical + opinions. To David Frascone for providing invaluable review of the + document. To Mark Jones for providing clarifying text on vendor + command codes and other vendor-specific indicators. To Victor + Pascual and Sebastien Decugis for new text and recommendations on + SCTP/DTLS. To Jouni Korhonen for taking over the editing task and + resolving last bits from versions 27 through 29. + + Special thanks to the Diameter extensibility design team, which + helped resolve the tricky question of mandatory AVPs and ABNF + semantics. The members of this team are as follows: + + Avi Lior, Jari Arkko, Glen Zorn, Lionel Morand, Mark Jones, Tolga + Asveren, Jouni Korhonen, and Glenn McGregor. + + Special thanks also to people who have provided invaluable comments + and inputs especially in resolving controversial issues: + + Glen Zorn, Yoshihiro Ohba, Marco Stura, Stephen Farrel, Pete Resnick, + Peter Saint-Andre, Robert Sparks, Krishna Prasad, Sean Turner, Barry + Leiba, and Pasi Eronen. + + Finally, we would like to thank the original authors of this + document: + + Pat Calhoun, John Loughney, Jari Arkko, Erik Guttman, and Glen Zorn. + + Their invaluable knowledge and experience has given us a robust and + flexible AAA protocol that many people have seen great value in + adopting. We greatly appreciate their support and stewardship for + the continued improvements of Diameter as a protocol. We would also + like to extend our gratitude to folks aside from the authors who have + + + + +Fajardo, et al. Standards Track [Page 147] + +RFC 6733 Diameter Base Protocol October 2012 + + + assisted and contributed to the original version of this document. + Their efforts significantly contributed to the success of Diameter. + +A.2. RFC 3588 + + The authors would like to thank Nenad Trifunovic, Tony Johansson and + Pankaj Patel for their participation in the pre-IETF Document Reading + Party. Allison Mankin, Jonathan Wood, and Bernard Aboba provided + invaluable assistance in working out transport issues and this was + also the case with Steven Bellovin in the security area. + + Paul Funk and David Mitton were instrumental in getting the Peer + State Machine correct, and our deep thanks go to them for their time. + + Text in this document was also provided by Paul Funk, Mark Eklund, + Mark Jones, and Dave Spence. Jacques Caron provided many great + comments as a result of a thorough review of the spec. + + The authors would also like to acknowledge the following people for + their contribution in the development of the Diameter protocol: + + Allan C. Rubens, Haseeb Akhtar, William Bulley, Stephen Farrell, + David Frascone, Daniel C. Fox, Lol Grant, Ignacio Goyret, Nancy + Greene, Peter Heitman, Fredrik Johansson, Mark Jones, Martin Julien, + Bob Kopacz, Paul Krumviede, Fergal Ladley, Ryan Moats, Victor Muslin, + Kenneth Peirce, John Schnizlein, Sumit Vakil, John R. Vollbrecht, and + Jeff Weisberg. + + Finally, Pat Calhoun would like to thank Sun Microsystems since most + of the effort put into this document was done while he was in their + employ. + +Appendix B. S-NAPTR Example + + As an example, consider a client that wishes to resolve aaa: + ex1.example.com. The client performs a NAPTR query for that domain, + and the following NAPTR records are returned: + + ;; order pref flags service regexp replacement + IN NAPTR 50 50 "s" "aaa:diameter.tls.tcp" "" + _diameter._tls.ex1.example.com + IN NAPTR 100 50 "s" "aaa:diameter.tcp" "" + _aaa._tcp.ex1.example.com + IN NAPTR 150 50 "s" "aaa:diameter.sctp" "" + _diameter._sctp.ex1.example.com + + This indicates that the server supports TLS, TCP, and SCTP in that + order. If the client supports TLS, TLS will be used, targeted to a + + + +Fajardo, et al. Standards Track [Page 148] + +RFC 6733 Diameter Base Protocol October 2012 + + + host determined by an SRV lookup of _diameter._tls.ex1.example.com. + That lookup would return: + + ;; Priority Weight Port Target + IN SRV 0 1 5060 server1.ex1.example.com + IN SRV 0 2 5060 server2.ex1.example.com + + As an alternative example, a client that wishes to resolve aaa: + ex2.example.com. The client performs a NAPTR query for that domain, + and the following NAPTR records are returned: + + ;; order pref flags service regexp replacement + IN NAPTR 150 50 "a" "aaa:diameter.tls.tcp" "" + server1.ex2.example.com + IN NAPTR 150 50 "a" "aaa:diameter.tls.tcp" "" + server2.ex2.example.com + + This indicates that the server supports TCP available at the returned + host names. + +Appendix C. Duplicate Detection + + As described in Section 9.4, accounting record duplicate detection is + based on session identifiers. Duplicates can appear for various + reasons: + + o Failover to an alternate server. Where close to real-time + performance is required, failover thresholds need to be kept low. + This may lead to an increased likelihood of duplicates. Failover + can occur at the client or within Diameter agents. + + o Failure of a client or agent after sending a record from non- + volatile memory, but prior to receipt of an application-layer ACK + and deletion of the record to be sent. This will result in + retransmission of the record soon after the client or agent has + rebooted. + + o Duplicates received from RADIUS gateways. Since the + retransmission behavior of RADIUS is not defined within [RFC2865], + the likelihood of duplication will vary according to the + implementation. + + o Implementation problems and misconfiguration. + + The T flag is used as an indication of an application-layer + retransmission event, e.g., due to failover to an alternate server. + It is defined only for request messages sent by Diameter clients or + agents. For instance, after a reboot, a client may not know whether + + + +Fajardo, et al. Standards Track [Page 149] + +RFC 6733 Diameter Base Protocol October 2012 + + + it has already tried to send the accounting records in its non- + volatile memory before the reboot occurred. Diameter servers MAY use + the T flag as an aid when processing requests and detecting duplicate + messages. However, servers that do this MUST ensure that duplicates + are found even when the first transmitted request arrives at the + server after the retransmitted request. It can be used only in cases + where no answer has been received from the server for a request and + the request is sent again, (e.g., due to a failover to an alternate + peer, due to a recovered primary peer or due to a client re-sending a + stored record from non-volatile memory such as after reboot of a + client or agent). + + In some cases, the Diameter accounting server can delay the duplicate + detection and accounting record processing until a post-processing + phase takes place. At that time records are likely to be sorted + according to the included User-Name and duplicate elimination is easy + in this case. In other situations, it may be necessary to perform + real-time duplicate detection, such as when credit limits are imposed + or real-time fraud detection is desired. + + In general, only generation of duplicates due to failover or re- + sending of records in non-volatile storage can be reliably detected + by Diameter clients or agents. In such cases, the Diameter client or + agents can mark the message as a possible duplicate by setting the T + flag. Since the Diameter server is responsible for duplicate + detection, it can choose whether or not to make use of the T flag, in + order to optimize duplicate detection. Since the T flag does not + affect interoperability, and it may not be needed by some servers, + generation of the T flag is REQUIRED for Diameter clients and agents, + but it MAY be implemented by Diameter servers. + + As an example, it can be usually be assumed that duplicates appear + within a time window of longest recorded network partition or device + fault, perhaps a day. So only records within this time window need + to be looked at in the backward direction. Secondly, hashing + techniques or other schemes, such as the use of the T flag in the + received messages, may be used to eliminate the need to do a full + search even in this set except for rare cases. + + The following is an example of how the T flag may be used by the + server to detect duplicate requests. + + A Diameter server MAY check the T flag of the received message to + determine if the record is a possible duplicate. If the T flag is + set in the request message, the server searches for a duplicate + within a configurable duplication time window backward and + forward. This limits database searching to those records where + the T flag is set. In a well-run network, network partitions and + + + +Fajardo, et al. Standards Track [Page 150] + +RFC 6733 Diameter Base Protocol October 2012 + + + device faults will presumably be rare events, so this approach + represents a substantial optimization of the duplicate detection + process. During failover, it is possible for the original record + to be received after the T-flag-marked record, due to differences + in network delays experienced along the path by the original and + duplicate transmissions. The likelihood of this occurring + increases as the failover interval is decreased. In order to be + able to detect duplicates that are out of order, the Diameter + server should use backward and forward time windows when + performing duplicate checking for the T-flag-marked request. For + example, in order to allow time for the original record to exit + the network and be recorded by the accounting server, the Diameter + server can delay processing records with the T flag set until a + time period TIME_WAIT + RECORD_PROCESSING_TIME has elapsed after + the closing of the original transport connection. After this time + period, it may check the T-flag-marked records against the + database with relative assurance that the original records, if + sent, have been received and recorded. + +Appendix D. Internationalized Domain Names + + To be compatible with the existing DNS infrastructure and simplify + host and domain name comparison, Diameter identities (FQDNs) are + represented in ASCII form. This allows the Diameter protocol to fall + in-line with the DNS strategy of being transparent from the effects + of Internationalized Domain Names (IDNs) by following the + recommendations in [RFC4690] and [RFC5890]. Applications that + provide support for IDNs outside of the Diameter protocol but + interacting with it SHOULD use the representation and conversion + framework described in [RFC5890], [RFC5891], and [RFC3492]. +</pre> + +</section> diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml index fe2389d57d..01c781d553 100644 --- a/lib/diameter/doc/src/diameter_tcp.xml +++ b/lib/diameter/doc/src/diameter_tcp.xml @@ -93,7 +93,8 @@ before configuring TLS capability on diameter transports.</p> <v>Reason = term()</v> <v>OwnOpt = {raddr, &ip_address;} | {rport, integer()} - | {port, integer()}</v> + | {port, integer()} + | {fragment_timer, infinity | 0..16#FFFFFFFF}</v> <v>SslOpt = {ssl_options, true | list()}</v> <v>TcpOpt = term()</v> </type> @@ -103,7 +104,6 @@ before configuring TLS capability on diameter transports.</p> The start function required by &man_transport;.</p> <p> -The only diameter_tcp-specific argument is the options list. Options <c>raddr</c> and <c>rport</c> specify the remote address and port for a connecting transport and are not valid for a listening transport. @@ -112,7 +112,18 @@ that should support TLS: a value of <c>true</c> results in a TLS handshake immediately upon connection establishment while <c>list()</c> specifies options to be passed to &ssl_connect2; or &ssl_accept2; -after capabilities exchange if TLS is negotiated. +after capabilities exchange if TLS is negotiated.</p> + +<p> +Option <c>fragment_timer</c> specifies the timeout, in milliseconds, +of a timer used to flush messages from the incoming byte +stream even if the number of bytes indicated in the Message Length +field of its Diameter Header have not yet been accumulated: +such a message is received over the transport interface after +two successive timeouts without the reception of additional bytes. +Defaults to 1000.</p> + +<p> Remaining options are any accepted by &ssl_connect3; or &gen_tcp_connect3; for a connecting transport, or &ssl_listen2; or &gen_tcp_listen2; for diff --git a/lib/diameter/doc/src/files.mk b/lib/diameter/doc/src/files.mk index 00ced3d91e..510786a7fb 100644 --- a/lib/diameter/doc/src/files.mk +++ b/lib/diameter/doc/src/files.mk @@ -2,7 +2,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 @@ -38,7 +38,9 @@ XML_REF4_FILES = \ XML_PART_FILES = \ user_man.xml -XML_EXTRA_FILES = +XML_EXTRA_FILES = \ + seealso.ent \ + diameter_soc_rfc6733.xml XML_CHAPTER_FILES = \ diameter_intro.xml \ @@ -50,5 +52,4 @@ XML_CHAPTER_FILES = \ BOOK_FILES = \ book.xml -GIF_FILES = \ - notes.gif +GIF_FILES = diff --git a/lib/diameter/doc/src/notes.gif b/lib/diameter/doc/src/notes.gif Binary files differdeleted file mode 100644 index e000cca26a..0000000000 --- a/lib/diameter/doc/src/notes.gif +++ /dev/null diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index 6fe22492e4..2daf84b0d4 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -42,6 +42,130 @@ first.</p> <!-- ===================================================================== --> +<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> + <item> + <p> + Fix erroneous watchdog transition from DOWN to INITIAL.</p> + <p> + This transition took place when a peer connection was + reestablished following a failed capabilities exchange. + RFC 3539 requires DOWN to transition into REOPEN.</p> + <p> + Own Id: OTP-10692</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add application_opt() request_errors to make the handling + of incoming requests containing decode errors + configurable.</p> + <p> + The value 'callback' ensures that a handle_request + callback takes place for all such requests, the default + being for diameter to answer 3xxx series errors itself.</p> + <p> + Own Id: OTP-10686</p> + </item> + <item> + <p> + Add transport_opt() length_errors.</p> + <p> + The value determines how messages received over the + transport interface with an incorrect Message Length are + dealt with.</p> + <p> + Own Id: OTP-10687</p> + </item> + <item> + <p> + Add commentary on RFC 6733 to Standards Compliance + chapter of the User's Guide.</p> + <p> + Own Id: OTP-10688</p> + </item> + <item> + <p> + Allow a 5xxx result code in an answer-message on peer + connections using the RFC 6733 common dictionary.</p> + <p> + RFC 6733 allows this while RFC 3588 does not. A + handle_request callback can return {answer_message, + 3000..3999|5000..5999} in the simplest case.</p> + <p> + Own Id: OTP-10759</p> + </item> + <item> + <p> + Add dictionaries for RFC 6733.</p> + <p> + Both the common and accounting dictionaries differ from + their RFC 3588 counterparts, which is reflected in + generated record definitions. Application configuration + on a service or transport determines the dictionary that + will be used on a given peer connection.</p> + <p> + Own Id: OTP-10760</p> + </item> + <item> + <p> + Allow a handle_request callback to control diameter's + setting of Result-Code and Failed-AVP.</p> + <p> + Setting errors = false in a returned #diameter_packet{} + disables the setting.</p> + <p> + Own Id: OTP-10761</p> + </item> + </list> + </section> + +</section> + <section><title>Diameter 1.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent index 9945bcadd3..76b9823f79 100644 --- a/lib/diameter/doc/src/seealso.ent +++ b/lib/diameter/doc/src/seealso.ent @@ -4,7 +4,7 @@ %CopyrightBegin% -Copyright Ericsson AB 2012. All Rights Reserved. +Copyright Ericsson AB 2012-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 @@ -41,7 +41,8 @@ significant. <!ENTITY mod_origin_state_id '<seealso marker="diameter#origin_state_id-0">diameter:origin_state_id/0</seealso>'> <!ENTITY mod_remove_transport '<seealso marker="diameter#remove_transport-2">diameter:remove_transport/2</seealso>'> <!ENTITY mod_service_info '<seealso marker="diameter#service_info-2">diameter:service_info/2</seealso>'> -<!ENTITY mod_services '<seealso marker="diameter#services-0">diameter:services/0</seealso>'> +<!ENTITY mod_services '<seealso marker="diameter#services-0">diameter:services/0</seealso>'> +<!ENTITY mod_session_id '<seealso marker="diameter#session_id-1">diameter:session_id/1</seealso>'> <!ENTITY mod_start_service '<seealso marker="diameter#start_service-2">diameter:start_service/2</seealso>'> <!ENTITY mod_stop_service '<seealso marker="diameter#stop_service-1">diameter:stop_service/1</seealso>'> <!ENTITY mod_subscribe '<seealso marker="diameter#subscribe-1">diameter:subscribe/1</seealso>'> diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl index beb577afaf..79c4dce541 100644 --- a/lib/diameter/include/diameter.hrl +++ b/lib/diameter/include/diameter.hrl @@ -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 @@ -25,7 +25,11 @@ -define(DIAMETER_APP_ID_ACCOUNTING, 3). -define(DIAMETER_APP_ID_RELAY, 16#FFFFFFFF). -%% Corresponding dictionaries: +%% Corresponding dictionaries. These macros are deprecated now that +%% there is an RFC6733 whose dictionaries are not strictly backwards +%% compatible. The RFC 6733 common and accounting dictionaries are +%% diameter_gen_base_rfc6733 and diameter_gen_acct_rfc6733 +%% respectively. -define(DIAMETER_DICT_COMMON, diameter_gen_base_rfc3588). -define(DIAMETER_DICT_ACCOUNTING, diameter_gen_base_accounting). -define(DIAMETER_DICT_RELAY, diameter_gen_relay). @@ -139,6 +143,6 @@ init_state, %% option 'state', initial callback state id, %% 32-bit unsigned application identifier = Dict:id() mutable = false, %% boolean(), do traffic callbacks modify state? - options = [{answer_errors, report}]}). %% | callback | discard - + options = [{answer_errors, report}, %% | callback | discard + {request_errors, answer_3xxx}]}). %% | callback | answer -endif. %% -ifdef(diameter_hrl). diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 13a6c462af..03aa557c2e 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -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 @@ -320,7 +320,7 @@ pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Acc) -> pack_avp(Name, 0, Avp, Acc) -> pack_AVP(Name, Avp, Acc); -pack_avp(_, Arity, Avp, Acc) -> +pack_avp(_, Arity, Avp, Acc) -> pack(Arity, Avp#diameter_avp.name, Avp, Acc). %% pack_AVP/3 @@ -332,7 +332,7 @@ pack_AVP(Name, Avp, Acc) -> Arity -> pack(Arity, 'AVP', Avp, Acc) end. - + %% 3588: %% %% DIAMETER_AVP_UNSUPPORTED 5001 diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index a08c204a23..df10c33268 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -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 @@ -109,7 +109,8 @@ ERL_COMPILE_FLAGS += \ +warn_unused_vars \ -pa $(ABS_EBIN) \ -I $(INCDIR) \ - -I gen + -I gen \ + $(STRICT_FLAGS) # -pa is to be able to include_lib from the include directory: the # path must contain the application name. @@ -119,10 +120,18 @@ ERL_COMPILE_FLAGS += \ # erl/hrl from dictionary file. gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia - $(dia_verbose)../bin/diameterc -o gen -i $(EBIN) $< + $(dia_verbose) \ + ../bin/diameterc -o gen -i $(EBIN) $< opt: $(TARGET_FILES) +# Compile with -Werror during development. Don't do this in the 'opt' +# target so that new warnings don't break the build. It's also +# convenient to have both targets when weeding out warnings isn't the +# priority. (Or when they're intentional, when debugging.) +strict: + $(MAKE) opt STRICT_FLAGS=-Werror + # Build unofficial patches with some degree of traceability. Refuse to # build if there are diffs from HEAD since that defeats the purpose. patch: @@ -134,11 +143,13 @@ debug: # The dictionary parser. gen/$(DICT_YRL).erl: compiler/$(DICT_YRL).yrl - $(yecc_verbose)$(ERLC) -Werror -o $(@D) $< + $(yecc_verbose) \ + $(ERLC) -Werror -o $(@D) $< # Generate the app file. $(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk - $(gen_verbose)M=`echo $(notdir $(APP_MODULES)) | tr ' ' ,`; \ + $(gen_verbose) \ + M=`echo $(notdir $(APP_MODULES)) | tr ' ' ,`; \ R=`echo $(REGISTERED) | tr ' ' ,`; \ sed -e 's;%VSN%;$(VSN);' \ -e "s;%MODULES%;$$M;" \ @@ -146,7 +157,8 @@ $(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk $< > $@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk - $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ + $(vsn_verbose) \ + sed -e 's;%VSN%;$(VSN);' $< > $@ app: $(APP_TARGET) $(APPUP_TARGET) dict: $(DICT_ERLS) @@ -247,11 +259,15 @@ release_docs_spec: # Dependencies # ---------------------------------------------------- -gen/diameter_gen_base_accounting.erl gen/diameter_gen_relay.erl \ -gen/diameter_gen_base_accounting.hrl gen/diameter_gen_relay.hrl: \ +gen/diameter_gen_base_accounting.erl gen/diameter_gen_base_accounting.hrl: \ $(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR) -gen/diameter_gen_base_rfc3588.erl gen/diameter_gen_base_rfc3588.hrl: \ +gen/diameter_gen_acct_rfc6733.erl gen/diameter_gen_acct_rfc6733.hrl: \ + $(EBIN)/diameter_gen_base_rfc6733.$(EMULATOR) + +gen/diameter_gen_relay.erl gen/diameter_gen_relay.hrl \ +gen/diameter_gen_base_rfc3588.erl gen/diameter_gen_base_rfc3588.hrl \ +gen/diameter_gen_base_rfc6733.erl gen/diameter_gen_base_rfc6733.hrl: \ $(COMPILER_MODULES:%=$(EBIN)/%.$(EMULATOR)) $(DICT_MODULES:gen/%=$(EBIN)/%.$(EMULATOR)): \ @@ -262,7 +278,7 @@ depend: depend.mk # Generate dependencies makefile. depend.mk: depend.sed $(MODULES:%=%.erl) Makefile - $(gen_verbose)(for f in $(MODULES); do \ + (for f in $(MODULES); do \ (echo $$f; cat $$f.erl) | sed -f $<; \ done) \ > $@ @@ -273,7 +289,7 @@ depend.mk: depend.sed $(MODULES:%=%.erl) Makefile .PHONY: debug opt release_docs_spec release_spec .PHONY: $(TARGET_DIRS:%/=%) $(TARGET_DIRS:%/=release_src_%) .PHONY: $(EXAMPLE_DIRS:%/=release_examples_%) -.PHONY: plt dialyze patch +.PHONY: plt dialyze patch strict # Keep intermediate files. .SECONDARY: $(DICT_ERLS) $(DICT_HRLS) gen/$(DICT_YRL:%=%.erl) diff --git a/lib/diameter/src/base/diameter.appup.src b/lib/diameter/src/base/diameter.appup.src index f6d772b534..359f434941 100644 --- a/lib/diameter/src/base/diameter.appup.src +++ b/lib/diameter/src/base/diameter.appup.src @@ -20,14 +20,21 @@ {"%VSN%", [ - {"0.9", [{restart_application, diameter}]}, - {"0.10", [{restart_application, diameter}]}, - {"1.0", [{restart_application, diameter}]}, - {"1.1", [{restart_application, diameter}]}, - {"1.2", [{restart_application, diameter}]}, + {"0.9", [{restart_application, diameter}]}, %% R14B03 + {"0.10", [{restart_application, diameter}]}, %% R14B04 + {"1.0", [{restart_application, diameter}]}, %% R15B + {"1.1", [{restart_application, diameter}]}, %% R15B01 + {"1.2", [{restart_application, diameter}]}, %% R15B02 {"1.2.1", [{restart_application, diameter}]}, - {"1.3", [{restart_application, diameter}]}, - {"1.3.1", [{restart_application, diameter}]} + {"1.3", [{restart_application, diameter}]}, %% R15B03 + {"1.3.1", [{restart_application, diameter}]}, + {"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}]}, @@ -37,6 +44,8 @@ {"1.2", [{restart_application, diameter}]}, {"1.2.1", [{restart_application, diameter}]}, {"1.3", [{restart_application, diameter}]}, - {"1.3.1", [{restart_application, diameter}]} + {"1.3.1", [{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 8f9901907a..490a1fa8aa 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.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 @@ -45,6 +45,7 @@ -export_type([evaluable/0, restriction/0, + remotes/0, sequence/0, app_alias/0, service_name/0, @@ -213,7 +214,7 @@ origin_state_id() -> -> any(). call(SvcName, App, Message, Options) -> - diameter_service:call(SvcName, {alias, App}, Message, Options). + diameter_traffic:send_request(SvcName, {alias, App}, Message, Options). call(SvcName, App, Message) -> call(SvcName, App, Message, []). @@ -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()} @@ -306,7 +314,8 @@ call(SvcName, App, Message) -> | {module, app_module()} | {state, any()} | {call_mutates_state, boolean()} - | {answer_errors, callback|report|discard}. + | {answer_errors, callback|report|discard} + | {request_errors, answer_3xxx|answer|callback}. -type app_alias() :: any(). @@ -332,8 +341,10 @@ call(SvcName, App, Message) -> | {capabilities_cb, evaluable()} | {capx_timeout, 'Unsigned32'()} | {disconnect_cb, evaluable()} - | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} + | {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 c6c3d2934d..9a443fead0 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-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 @@ -47,14 +47,13 @@ -module(diameter_capx). --export([build_CER/1, - recv_CER/2, - recv_CEA/2, +-export([build_CER/2, + recv_CER/3, + recv_CEA/3, make_caps/2]). -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_gen_base_rfc3588.hrl"). -define(SUCCESS, 2001). %% DIAMETER_SUCCESS -define(NOAPP, 5010). %% DIAMETER_NO_COMMON_APPLICATION @@ -67,27 +66,31 @@ -type tried(T) :: {ok, T} | {error, {term(), list()}}. --spec build_CER(#diameter_caps{}) - -> tried(#diameter_base_CER{}). +-spec build_CER(#diameter_caps{}, module()) + -> tried(CER) + when CER :: tuple(). -build_CER(Caps) -> - try_it([fun bCER/1, Caps]). +build_CER(Caps, Dict) -> + try_it([fun bCER/2, Caps, Dict]). --spec recv_CER(#diameter_base_CER{}, #diameter_service{}) +-spec recv_CER(CER, #diameter_service{}, module()) -> tried({[diameter:'Unsigned32'()], #diameter_caps{}, - #diameter_base_CEA{}}). + CEA}) + when CER :: tuple(), + CEA :: tuple(). -recv_CER(CER, Svc) -> - try_it([fun rCER/2, CER, Svc]). +recv_CER(CER, Svc, Dict) -> + try_it([fun rCER/3, CER, Svc, Dict]). --spec recv_CEA(#diameter_base_CEA{}, #diameter_service{}) +-spec recv_CEA(CEA, #diameter_service{}, module()) -> tried({[diameter:'Unsigned32'()], [diameter:'Unsigned32'()], - #diameter_caps{}}). + #diameter_caps{}}) + when CEA :: tuple(). -recv_CEA(CEA, Svc) -> - try_it([fun rCEA/2, CEA, Svc]). +recv_CEA(CEA, Svc, Dict) -> + try_it([fun rCEA/3, CEA, Svc, Dict]). make_caps(Caps, Opts) -> try_it([fun mk_caps/2, Caps, Opts]). @@ -161,16 +164,60 @@ ipaddr(A) -> ?THROW(T) end. -%% bCER/1 +%% bCER/2 %% %% Build a CER record to send to a remote peer. %% Use the fact that diameter_caps has the same field names as CER. -bCER(#diameter_caps{} = Rec) -> - #diameter_base_CER{} - = list_to_tuple([diameter_base_CER | tl(tuple_to_list(Rec))]). +bCER(#diameter_caps{} = Rec, Dict) -> + Values = lists:zip(Dict:'#info-'(diameter_base_CER, fields), + tl(tuple_to_list(Rec))), + Dict:'#new-'(diameter_base_CER, [{K, map(K, V, Dict)} + || {K,V} <- Values]). -%% rCER/2 +%% 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 %% %% Build a CEA record to send to a remote peer in response to an %% incoming CER. RFC 3588 gives no guidance on what should be sent @@ -214,12 +261,9 @@ bCER(#diameter_caps{} = Rec) -> %% TLS 1 %% This node supports TLS security, as defined by [TLS]. -rCER(CER, #diameter_service{capabilities = LCaps} = Svc) -> - #diameter_base_CEA{} - = CEA - = cea_from_cer(bCER(LCaps)), - - RCaps = capx_to_caps(CER), +rCER(CER, #diameter_service{capabilities = LCaps} = Svc, Dict) -> + CEA = cea_from_cer(bCER(LCaps, Dict), Dict), + RCaps = capx_to_caps(CER, Dict), SApps = common_applications(LCaps, RCaps, Svc), {SApps, @@ -227,17 +271,18 @@ rCER(CER, #diameter_service{capabilities = LCaps} = Svc) -> build_CEA(SApps, LCaps, RCaps, - CEA#diameter_base_CEA{'Result-Code' = ?SUCCESS})}. + Dict, + Dict:'#set-'({'Result-Code', ?SUCCESS}, CEA))}. -build_CEA([], _, _, CEA) -> - CEA#diameter_base_CEA{'Result-Code' = ?NOAPP}; +build_CEA([], _, _, Dict, CEA) -> + Dict:'#set-'({'Result-Code', ?NOAPP}, CEA); -build_CEA(_, LCaps, RCaps, CEA) -> +build_CEA(_, LCaps, RCaps, Dict, CEA) -> case common_security(LCaps, RCaps) of [] -> - CEA#diameter_base_CEA{'Result-Code' = ?NOSECURITY}; + Dict:'#set-'({'Result-Code', ?NOSECURITY}, CEA); [_] = IS -> - CEA#diameter_base_CEA{'Inband-Security-Id' = IS} + Dict:'#set-'({'Inband-Security-Id', IS}, CEA) end. %% common_security/2 @@ -275,46 +320,41 @@ cs(LS, RS) -> %% practice something there may be a need for more synchronization %% than notification by way of an event subscription offers. -%% cea_from_cer/1 +%% cea_from_cer/2 %% CER is a subset of CEA, the latter adding Result-Code and a few %% more AVP's. -cea_from_cer(#diameter_base_CER{} = CER) -> - lists:foldl(fun(F,A) -> to_cea(CER, F, A) end, - #diameter_base_CEA{}, - record_info(fields, diameter_base_CER)). - -to_cea(CER, Field, CEA) -> - try ?BASE:'#get-'(Field, CER) of - V -> ?BASE:'#set-'({Field, V}, CEA) - catch - error: _ -> CEA - end. - -%% rCEA/2 +cea_from_cer(CER, Dict) -> + [diameter_base_CER | Values] = Dict:'#get-'(CER), + Dict:'#set-'(Values, Dict:'#new-'(diameter_base_CEA)). -rCEA(CEA, #diameter_service{capabilities = LCaps} = Svc) -> - RCaps = capx_to_caps(CEA), +%% rCEA/3 + +rCEA(CEA, #diameter_service{capabilities = LCaps} = Svc, Dict) -> + RCaps = capx_to_caps(CEA, Dict), SApps = common_applications(LCaps, RCaps, Svc), IS = common_security(LCaps, RCaps), {SApps, IS, RCaps}. -%% capx_to_caps/1 - -capx_to_caps(#diameter_base_CEA{'Origin-Host' = OH, - 'Origin-Realm' = OR, - 'Host-IP-Address' = IP, - 'Vendor-Id' = VId, - 'Product-Name' = PN, - 'Origin-State-Id' = OSI, - 'Supported-Vendor-Id' = SV, - 'Auth-Application-Id' = Auth, - 'Inband-Security-Id' = IS, - 'Acct-Application-Id' = Acct, - 'Vendor-Specific-Application-Id' = VSA, - 'Firmware-Revision' = FR, - 'AVP' = X}) -> +%% capx_to_caps/2 + +capx_to_caps(CEX, Dict) -> + [OH, OR, IP, VId, PN, OSI, SV, Auth, IS, Acct, VSA, FR, X] + = Dict:'#get-'(['Origin-Host', + 'Origin-Realm', + 'Host-IP-Address', + 'Vendor-Id', + 'Product-Name', + 'Origin-State-Id', + 'Supported-Vendor-Id', + 'Auth-Application-Id', + 'Inband-Security-Id', + 'Acct-Application-Id', + 'Vendor-Specific-Application-Id', + 'Firmware-Revision', + 'AVP'], + CEX), #diameter_caps{origin_host = OH, origin_realm = OR, vendor_id = VId, @@ -327,10 +367,7 @@ capx_to_caps(#diameter_base_CEA{'Origin-Host' = OH, acct_application_id = Acct, vendor_specific_application_id = VSA, firmware_revision = FR, - avp = X}; - -capx_to_caps(#diameter_base_CER{} = CER) -> - capx_to_caps(cea_from_cer(CER)). + avp = X}. %% --------------------------------------------------------------------------- %% --------------------------------------------------------------------------- @@ -365,13 +402,12 @@ app_union(#diameter_caps{auth_application_id = U, vendor_specific_application_id = V}) -> set_list(U ++ C ++ lists:flatmap(fun vsa_apps/1, V)). -vsa_apps(#'diameter_base_Vendor-Specific-Application-Id' - {'Auth-Application-Id' = U, - 'Acct-Application-Id' = C}) -> - U ++ C; -vsa_apps(L) -> - Rec = ?BASE:'#new-'('diameter_base_Vendor-Specific-Application-Id', L), - vsa_apps(Rec). +vsa_apps([_ | [_,_] = Ids]) -> + lists:append(Ids); +vsa_apps(Rec) + when is_tuple(Rec) -> + [_|T] = tuple_to_list(Rec), + vsa_apps(T). %% It's a configuration error for a locally advertised application not %% to be represented in Apps. Don't just match on lists:keyfind/3 in diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 0b0bfe3f0a..e446a0209c 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-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 @@ -26,7 +26,7 @@ decode_header/1, sequence_numbers/1, hop_by_hop_id/2, - msg_name/1, + msg_name/2, msg_id/1]). %% Towards generated encoders (from diameter_gen.hrl). @@ -99,13 +99,13 @@ e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> Eid:32, Avps/binary>>}; -e(Mod0, #diameter_packet{header = Hdr, msg = Msg} = Pkt) -> +e(Mod, #diameter_packet{header = Hdr, msg = Msg} = Pkt) -> #diameter_header{version = Vsn, hop_by_hop_id = Hid, end_to_end_id = Eid} = Hdr, - {Mod, MsgName} = rec2msg(Mod0, Msg), + MsgName = rec2msg(Mod, Msg), {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr), Flags = make_flags(Flags0, Hdr), @@ -192,11 +192,11 @@ encode_avps(Avps) -> %% msg_header/3 msg_header(Mod, 'answer-message' = MsgName, Header) -> - ?BASE = Mod, + 0 = Mod:id(), %% assert #diameter_header{application_id = Aid, cmd_code = Code} = Header, - {-1, Flags, ?DIAMETER_APP_ID_COMMON} = ?BASE:msg_header(MsgName), + {-1, Flags, ?DIAMETER_APP_ID_COMMON} = Mod:msg_header(MsgName), {Code, Flags, Aid}; msg_header(Mod, MsgName, _) -> @@ -204,22 +204,12 @@ msg_header(Mod, MsgName, _) -> %% rec2msg/2 -rec2msg(_, ['answer-message' = M | _]) -> - {?BASE, M}; - -rec2msg(Mod, [MsgName|_]) - when is_atom(MsgName) -> - {Mod, MsgName}; +rec2msg(_, [Name|_]) + when is_atom(Name) -> + Name; rec2msg(Mod, Rec) -> - R = element(1, Rec), - A = 'answer-message', - case ?BASE:msg2rec(A) of - R -> - {?BASE, A}; - _ -> - {Mod, Mod:rec2msg(R)} - end. + Mod:rec2msg(element(1, Rec)). %%% --------------------------------------------------------------------------- %%% # decode/2 @@ -243,20 +233,19 @@ decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) -> end; %% Otherwise decode using the dictionary. -decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) - when is_atom(Mod) -> +decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) -> #diameter_header{cmd_code = CmdCode, is_request = IsRequest, is_error = IsError} = Hdr, - {M, MsgName} = if IsError andalso not IsRequest -> - {?BASE, 'answer-message'}; - true -> - {Mod, Mod:msg_name(CmdCode, IsRequest)} - end, + MsgName = if IsError andalso not IsRequest -> + 'answer-message'; + true -> + Mod:msg_name(CmdCode, IsRequest) + end, - decode_avps(MsgName, M, Pkt, collect_avps(Pkt)); + decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt)); decode(Id, Mod, Bin) when is_bitstring(Bin) -> @@ -360,15 +349,15 @@ hop_by_hop_id(Id, <<H:12/binary, _:32, T/binary>>) -> <<H/binary, Id:32, T/binary>>. %%% --------------------------------------------------------------------------- -%%% # msg_name/1 +%%% # msg_name/2 %%% --------------------------------------------------------------------------- -msg_name(#diameter_header{application_id = ?APP_ID_COMMON, - cmd_code = C, - is_request = R}) -> - ?BASE:msg_name(C,R); +msg_name(Dict0, #diameter_header{application_id = ?APP_ID_COMMON, + cmd_code = C, + is_request = R}) -> + Dict0:msg_name(C,R); -msg_name(Hdr) -> +msg_name(_, Hdr) -> msg_id(Hdr). %% Note that messages in different applications could have the same diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 63d28f25a2..3a2e0d2140 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-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 @@ -113,15 +113,22 @@ -define(VALUES(Rec), tl(tuple_to_list(Rec))). +%% The RFC 3588 common dictionary is used to validate capabilities +%% configuration. That a given transport may use the RFC 6733 +%% dictionary is of no consequence. +-define(BASE, diameter_gen_base_rfc3588). + %%% The return values below assume the server diameter_config is started. %%% The functions will exit if it isn't. %% -------------------------------------------------------------------------- -%% # start_service(SvcName, Opts) -%% -%% Output: ok | {error, Reason} +%% # start_service/2 %% -------------------------------------------------------------------------- +-spec start_service(diameter:service_name(), [diameter:service_opt()]) + -> ok + | {error, term()}. + start_service(SvcName, Opts) when is_list(Opts) -> start_rc(sync(SvcName, {start_service, SvcName, Opts})). @@ -134,22 +141,23 @@ start_rc(timeout) -> {error, application_not_started}. %% -------------------------------------------------------------------------- -%% # stop_service(SvcName) -%% -%% Output: ok +%% # stop_service/1 %% -------------------------------------------------------------------------- +-spec stop_service(diameter:service_name()) + -> ok. + stop_service(SvcName) -> sync(SvcName, {stop_service, SvcName}). %% -------------------------------------------------------------------------- -%% # add_transport(SvcName, {Type, Opts}) -%% -%% Input: Type = connect | listen -%% -%% Output: {ok, Ref} | {error, Reason} +%% # add_transport/2 %% -------------------------------------------------------------------------- +-spec add_transport(diameter:service_name(), {connect|listen, [diameter:transport_opt()]}) + -> {ok, diameter:transport_ref()} + | {error, term()}. + add_transport(SvcName, {T, Opts}) when is_list(Opts), (T == connect orelse T == listen) -> sync(SvcName, {add, SvcName, T, Opts}). @@ -171,6 +179,10 @@ add_transport(SvcName, {T, Opts}) %% Output: ok | {error, Reason} %% -------------------------------------------------------------------------- +-spec remove_transport(diameter:service_name(), diameter:transport_pred()) + -> ok + | {error, term()}. + remove_transport(SvcName, Pred) -> try sync(SvcName, {remove, SvcName, pred(Pred)}) @@ -473,6 +485,10 @@ stop(SvcName) -> %% add/3 +%% Can't check for a single common dictionary since a transport may +%% restrict applications so that that there's one while the service +%% has many. + add(SvcName, Type, Opts) -> %% Ensure usable capabilities. diameter_service:merge_service/2 %% depends on this. @@ -545,7 +561,7 @@ make_config(SvcName, Opts) -> [] == Apps andalso ?THROW(no_apps), %% Use the fact that diameter_caps has the same field names as CER. - Fields = diameter_gen_base_rfc3588:'#info-'(diameter_base_CER) -- ['AVP'], + Fields = ?BASE:'#info-'(diameter_base_CER) -- ['AVP'], COpts = [T || {K,_} = T <- Opts, lists:member(K, Fields)], Caps = make_caps(#diameter_caps{}, COpts), @@ -557,7 +573,6 @@ make_config(SvcName, Opts) -> {false, monitor}, {?NOMASK, sequence}, {nodes, restrict_connections}]), - %% share_peers and use_shared_peers are currently undocumented. #service{name = SvcName, rec = #diameter_service{applications = Apps, @@ -572,23 +587,31 @@ opt(K, false = B) 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 -> @@ -608,14 +631,14 @@ opt(sequence = K, F) -> E:R -> ?THROW({value, {K, E, R, ?STACK}}) end; - + opt(K, _) -> ?THROW({value, K}). sequence({H,N} = T) when 0 =< N, N =< 32, 0 =< H, 0 == H bsr N -> T; - + sequence(_) -> ?THROW({value, sequence}). @@ -629,7 +652,8 @@ make_caps(Caps, Opts) -> %% Validate types by encoding a CER. encode_CER(Opts) -> - {ok, CER} = diameter_capx:build_CER(make_caps(?EXAMPLE_CAPS, Opts)), + {ok, CER} = diameter_capx:build_CER(make_caps(?EXAMPLE_CAPS, Opts), + ?BASE), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = 0, @@ -653,15 +677,17 @@ app_acc({application, Opts}, Acc) -> [Dict, Mod] = get_opt([dictionary, module], Opts), Alias = get_opt(alias, Opts, Dict), ModS = get_opt(state, Opts, Alias), - M = get_opt(call_mutates_state, Opts, false), - A = get_opt(answer_errors, Opts, report), + M = get_opt(call_mutates_state, Opts, false, [true]), + A = get_opt(answer_errors, Opts, report, [callback, discard]), + P = get_opt(request_errors, Opts, answer_3xxx, [answer, callback]), [#diameter_app{alias = Alias, dictionary = Dict, id = cb(Dict, id), module = init_mod(Mod), init_state = ModS, - mutable = init_mutable(M), - options = [{answer_errors, init_answers(A)}]} + mutable = M, + options = [{answer_errors, A}, + {request_errors, P}]} | Acc]; app_acc(_, Acc) -> Acc. @@ -690,20 +716,16 @@ init_cb(List) -> V <- [proplists:get_value(F, List, D)]], #diameter_callback{} = list_to_tuple([diameter_callback | Values]). -init_mutable(M) - when M == true; - M == false -> - M; -init_mutable(M) -> - ?THROW({call_mutates_state, M}). - -init_answers(A) - when callback == A; - report == A; - discard == A -> - A; -init_answers(A) -> - ?THROW({answer_errors, A}). +%% Retreive and validate. +get_opt(Key, List, Def, Other) -> + init_opt(Key, get_opt(Key, List, Def), [Def|Other]). + +init_opt(_, V, [V|_]) -> + V; +init_opt(Name, V, [_|Vals]) -> + init_opt(Name, V, Vals); +init_opt(Name, V, []) -> + ?THROW({Name, V}). %% Get a single value at the specified key. get_opt(Keys, List) diff --git a/lib/diameter/src/base/diameter_internal.hrl b/lib/diameter/src/base/diameter_internal.hrl index 63b35550a8..4b672aa071 100644 --- a/lib/diameter/src/base/diameter_internal.hrl +++ b/lib/diameter/src/base/diameter_internal.hrl @@ -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 @@ -58,8 +58,6 @@ -define(APP_ID_COMMON, 0). -define(APP_ID_RELAY, 16#FFFFFFFF). --define(BASE, diameter_gen_base_rfc3588). - %%% --------------------------------------------------------- %%% RFC 3588, ch 2.6 Peer table diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl index 25c9eab4cb..dfc76eb76e 100644 --- a/lib/diameter/src/base/diameter_peer.erl +++ b/lib/diameter/src/base/diameter_peer.erl @@ -31,7 +31,7 @@ send/2, close/1, abort/1, - notify/2]). + notify/3]). %% Server start. -export([start_link/0]). @@ -63,11 +63,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 @@ -123,7 +123,7 @@ pair([_ | Rest], Mods, Acc) -> pair(Rest, Mods, Acc); %% No transport_module or transport_config: defaults. -pair([], [], []) -> +pair([], [], []) -> [{[?DEFAULT_TMOD], ?DEFAULT_TCFG, ?DEFAULT_TTMO}]; %% One transport_module, one transport_config. @@ -272,7 +272,7 @@ handle_cast(Msg, State) -> %% Remote service is distributing a message. handle_info({notify, SvcName, T}, S) -> - bang(diameter_service:whois(SvcName), T), + diameter_service:notify(SvcName, T), {noreply, S}; handle_info(Info, State) -> @@ -304,13 +304,6 @@ code_change(_OldVsn, State, _Extra) -> ifc_send(Pid, T) -> Pid ! {diameter, T}. -%% bang/2 - -bang(undefined = No, _) -> - No; -bang(Pid, T) -> - Pid ! T. - %% call/1 call(Request) -> diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 5dab6214b1..66342f7b62 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -18,10 +18,10 @@ %% %% -%% This module implements (as a process) the RFC 3588 Peer State +%% This module implements (as a process) the RFC 3588/6733 Peer State %% Machine modulo the necessity of adapting the peer election to the -%% fact that we don't know the identity of a peer until we've -%% received a CER/CEA from it. +%% fact that we don't know the identity of a peer until we've received +%% a CER/CEA from it. %% -module(diameter_peer_fsm). @@ -46,16 +46,19 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_gen_base_rfc3588.hrl"). %% Values of Disconnect-Cause in DPR. --define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU'). --define(REBOOT, ?'DIAMETER_BASE_DISCONNECT-CAUSE_REBOOTING'). --define(BUSY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_BUSY'). +-define(GOAWAY, 2). %% DO_NOT_WANT_TO_TALK_TO_YOU +-define(BUSY, 1). %% BUSY +-define(REBOOT, 0). %% REBOOTING +%% Values of Inband-Security-Id. -define(NO_INBAND_SECURITY, 0). -define(TLS, 1). +%% Note that the a common dictionary hrl is purposely not included +%% since the common dictionary is an argument to start/3. + %% Keys in process dictionary. -define(CB_KEY, cb). %% capabilities callback -define(DPR_KEY, dpr). %% disconnect callback @@ -100,11 +103,13 @@ | {'Wait-CEA', uint32(), uint32()} | 'Open', mode :: accept | connect | {connect, reference()}, - parent :: pid(), %% watchdog process - transport :: pid(), %% transport process + parent :: pid(), %% watchdog process + transport :: pid(), %% transport process + dictionary :: module(), %% common dictionary service :: #diameter_service{}, - dpr = false :: false | {uint32(), uint32()}}). + dpr = false :: false | {uint32(), uint32()}, %% | hop by hop and end to end identifiers + length_errors :: exit | handle | discard}). %% There are non-3588 states possible as a consequence of 5.6.1 of the %% standard and the corresponding problem for incoming CEA's: we don't @@ -126,24 +131,18 @@ %% State Machine rather than closer to the transport. This is what we %% now do below: connect/accept call diameter_watchdog and return the %% pid of the watchdog process, and the watchdog in turn calls start/3 -%% below to start the process implementing the Peer State Machine. The -%% former is a "peer" in diameter_service while the latter is a -%% "conn". In a sense, diameter_service sees the watchdog as -%% implementing the Peer State Machine and the process implemented -%% here as being the transport, not being aware of the watchdog at -%% all. +%% below to start the process implementing the Peer State Machine. %% -%%% --------------------------------------------------------------------------- -%%% # start({connect|accept, Ref}, Opts, Service) -%%% -%%% Output: Pid -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # start/3 +%% --------------------------------------------------------------------------- -spec start(T, [Opt], {diameter:sequence(), - diameter:restriction(), + [node()], + module(), #diameter_service{}}) - -> pid() + -> {reference(), pid()} when T :: {connect|accept, diameter:transport_ref()}, Opt :: diameter:transport_opt(). @@ -152,9 +151,15 @@ %% specified on the transport in question. Check here that the list is %% still non-empty. -start({_,_} = Type, Opts, MS) -> - {ok, Pid} = diameter_peer_fsm_sup:start_child({self(), Type, Opts, MS}), - Pid. +start({_,_} = Type, Opts, S) -> + Ack = make_ref(), + T = {Ack, self(), Type, Opts, S}, + {ok, Pid} = diameter_peer_fsm_sup:start_child(T), + try + {erlang:monitor(process, Pid), Pid} + after + Pid ! Ack + end. start_link(T) -> {ok, _} = proc_lib:start_link(?MODULE, @@ -163,8 +168,8 @@ start_link(T) -> infinity, diameter_lib:spawn_opts(server, [])). -%%% --------------------------------------------------------------------------- -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- %% init/1 @@ -172,12 +177,14 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, - capabilities = LCaps} - = Svc}}) -> - [] /= Apps orelse ?ERROR({no_apps, T, Opts}), +i({Ack, WPid, {M, Ref} = T, Opts, {Mask, + Nodes, + Dict0, + #diameter_service{capabilities = LCaps} + = Svc}}) -> + erlang:monitor(process, WPid), + wait(Ack, WPid), putr(?DWA_KEY, dwa(LCaps)), - {M, Ref} = T, diameter_stats:reg(Ref), {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]), putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}), @@ -185,23 +192,39 @@ i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, putr(?REF_KEY, Ref), putr(?SEQUENCE_KEY, Mask), putr(?RESTRICT_KEY, Nodes), - erlang:monitor(process, WPid), - {TPid, Addrs} = start_transport(T, Rest, Svc), + Tmo = proplists:get_value(capx_timeout, Opts, ?EVENT_TIMEOUT), ?IS_TIMEOUT(Tmo) orelse ?ERROR({invalid, {capx_timeout, Tmo}}), + OnLengthErr = proplists:get_value(length_errors, Opts, exit), + lists:member(OnLengthErr, [exit, handle, discard]) + orelse ?ERROR({invalid, {length_errors, OnLengthErr}}), + + {TPid, Addrs} = start_transport(T, Rest, Svc), + #state{state = {'Wait-Conn-Ack', Tmo}, parent = WPid, transport = TPid, + dictionary = Dict0, mode = M, - service = svc(Svc, Addrs)}. + service = svc(Svc, Addrs), + length_errors = OnLengthErr}. %% The transport returns its local ip addresses so that different %% 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 so as not to crash the -%% service. +%% 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.) +wait(Ref, Pid) -> + receive + Ref -> + ok; + {'DOWN', _, process, Pid, _} = D -> + exit({shutdown, D}) + end. start_transport(T, Opts, #diameter_service{capabilities = LCaps} = Svc) -> Addrs0 = LCaps#diameter_caps.host_ip_address, @@ -274,13 +297,12 @@ handle_info(T, #state{} = State) -> {noreply, S}; {stop, Reason} -> ?LOG(stop, Reason), - x(Reason, State); + {stop, {shutdown, Reason}, State}; stop -> ?LOG(stop, T), - x(T, State) + {stop, {shutdown, T}, State} catch exit: {diameter_codec, encode, _} = Reason -> - close_wd(Reason, State#state.parent), ?LOG(stop, Reason), %% diameter_codec:encode/2 emits an error report. Only %% indicate the probable reason here. @@ -300,10 +322,6 @@ handle_info(T, #state{} = State) -> %% succesfully encoded. It's not checked at diameter:add_transport/2 %% since this can be called before creating the service. -x(Reason, #state{} = S) -> - close_wd(Reason, S), - {stop, {shutdown, Reason}, S}. - %% terminate/2 terminate(_, _) -> @@ -314,8 +332,8 @@ terminate(_, _) -> code_change(_, State, _) -> {ok, State}. -%%% --------------------------------------------------------------------------- -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- putr(Key, Val) -> put({?MODULE, Key}, Val). @@ -378,9 +396,8 @@ transition({diameter, {recv, Pkt}}, S) -> recv(Pkt, S); %% Timeout when still in the same state ... -transition({timeout = T, PS}, #state{state = PS} = S) -> - close({capx(PS), T}, S), - stop; +transition({timeout = T, PS}, #state{state = PS}) -> + {stop, {capx(PS), T}}; %% ... or not. transition({timeout, _}, _) -> @@ -393,8 +410,6 @@ transition({send, Msg}, #state{transport = TPid}) -> %% Request for graceful shutdown at remove_transport, stop_service of %% application shutdown. -transition({shutdown = T, Pid}, S) -> - transition({T, Pid, transport}, S); transition({shutdown, Pid, Reason}, #state{parent = Pid, dpr = false} = S) -> dpr(Reason, S); transition({shutdown, Pid, _}, #state{parent = Pid}) -> @@ -454,18 +469,19 @@ start_next(#state{service = Svc0} = S) -> send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, mode = {connect, Remote}, service = #diameter_service{capabilities = LCaps}, - transport = TPid} + transport = TPid, + dictionary = Dict} = S) -> OH = LCaps#diameter_caps.origin_host, req_send_CER(OH, Remote) orelse - close({already_connected, Remote, LCaps}, S), + close({already_connected, Remote, LCaps}), CER = build_CER(S), ?LOG(send, 'CER'), #diameter_packet{header = #diameter_header{end_to_end_id = Eid, hop_by_hop_id = Hid}} = Pkt - = encode(CER), + = encode(CER, Dict), send(TPid, Pkt), start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}). @@ -487,42 +503,29 @@ start_timer(Tmo, #state{state = PS} = S) -> %% build_CER/1 -build_CER(#state{service = #diameter_service{capabilities = LCaps}}) -> - {ok, CER} = diameter_capx:build_CER(LCaps), +build_CER(#state{service = #diameter_service{capabilities = LCaps}, + dictionary = Dict}) -> + {ok, CER} = diameter_capx:build_CER(LCaps, Dict), CER. -%% encode/1 +%% encode/2 -encode(Rec) -> +encode(Rec, Dict) -> Seq = diameter_session:sequence({_,_} = getr(?SEQUENCE_KEY)), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, - diameter_codec:encode(?BASE, #diameter_packet{header = Hdr, - msg = Rec}). + diameter_codec:encode(Dict, #diameter_packet{header = Hdr, + msg = Rec}). %% recv/2 -%% RFC 3588 has result code 5015 for an invalid length but if a -%% transport is detecting message boundaries using the length header -%% then a length error will likely lead to further errors. - -recv(#diameter_packet{header = #diameter_header{length = Len} - = Hdr, - bin = Bin}, - S) - when Len < 20; - (0 /= Len rem 4 orelse bit_size(Bin) /= 8*Len) -> - discard(invalid_message_length, recv, [size(Bin), - bit_size(Bin) rem 8, - Hdr, - S]); - recv(#diameter_packet{header = #diameter_header{} = Hdr} = Pkt, - #state{parent = Pid} + #state{parent = Pid, + dictionary = Dict0} = S) -> - Name = diameter_codec:msg_name(Hdr), + Name = diameter_codec:msg_name(Dict0, Hdr), Pid ! {recv, self(), Name, Pkt}, diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received rcv(Name, Pkt, S); @@ -531,29 +534,52 @@ recv(#diameter_packet{header = undefined, bin = Bin} = Pkt, S) -> - recv(Pkt#diameter_packet{header = diameter_codec:decode_header(Bin)}, S); + recv(diameter_codec:decode_header(Bin), Pkt, S); -recv(Bin, S) - when is_binary(Bin) -> - recv(#diameter_packet{bin = Bin}, S); +recv(Bin, S) -> + recv(#diameter_packet{bin = Bin}, S). -recv(#diameter_packet{header = false} = Pkt, S) -> - discard(truncated_header, recv, [Pkt, S]). +%% recv/3 -msg_id({_,_,_} = T, _) -> - T; -msg_id(_, Hdr) -> - diameter_codec:msg_id(Hdr). +recv(#diameter_header{length = Len} + = H, + #diameter_packet{bin = Bin} + = Pkt, + #state{length_errors = E} + = S) + when E == handle; + 0 == Len rem 4, bit_size(Bin) == 8*Len -> + recv(Pkt#diameter_packet{header = H}, S); + +recv(#diameter_header{} + = H, + #diameter_packet{bin = Bin}, + #state{length_errors = E} + = S) -> + invalid(E, + invalid_message_length, + recv, + [size(Bin), bit_size(Bin) rem 8, H, S]); -%% Treat invalid length as a transport error and die. Especially in -%% the TCP case, in which there's no telling where the next message -%% begins in the incoming byte stream, keeping a crippled connection -%% alive may just make things worse. +recv(false, Pkt, #state{length_errors = E} = S) -> + invalid(E, truncated_header, recv, [Pkt, S]). -discard(Reason, F, A) -> +%% Note that counters here only count discarded messages. +invalid(E, Reason, F, A) -> diameter_stats:incr(Reason), + abort(E, Reason, F, A). + +abort(exit, Reason, F, A) -> diameter_lib:warning_report(Reason, {?MODULE, F, A}), - throw({?MODULE, abort, Reason}). + throw({?MODULE, abort, Reason}); + +abort(_, _, _, _) -> + ok. + +msg_id({_,_,_} = T, _) -> + T; +msg_id(_, Hdr) -> + {_,_,_} = diameter_codec:msg_id(Hdr). %% rcv/3 @@ -607,13 +633,13 @@ send(Pid, Msg) -> %% handle_request/3 -handle_request(Type, #diameter_packet{} = Pkt, S) -> +handle_request(Type, #diameter_packet{} = Pkt, #state{dictionary = D} = S) -> ?LOG(recv, Type), - send_answer(Type, diameter_codec:decode(?BASE, Pkt), S). + send_answer(Type, diameter_codec:decode(D, Pkt), S). %% send_answer/3 -send_answer(Type, ReqPkt, #state{transport = TPid} = S) -> +send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) -> #diameter_packet{header = H, transport_data = TD} = ReqPkt, @@ -630,13 +656,15 @@ send_answer(Type, ReqPkt, #state{transport = TPid} = S) -> msg = Msg, transport_data = TD}, - send(TPid, diameter_codec:encode(?BASE, Pkt)), + send(TPid, diameter_codec:encode(Dict, Pkt)), eval(PostF, S). eval([F|A], S) -> apply(F, A ++ [S]); eval(ok, S) -> - S. + S; +eval(T, _) -> + close(T). %% build_answer/3 @@ -647,11 +675,11 @@ build_answer('CER', is_error = false}, errors = []} = Pkt, - S) -> - {SupportedApps, RCaps, #diameter_base_CEA{'Result-Code' = RC, - 'Inband-Security-Id' = IS} - = CEA} - = recv_CER(CER, S), + #state{dictionary = Dict0} + = S) -> + {SupportedApps, RCaps, CEA} = recv_CER(CER, S), + + [RC, IS] = Dict0:'#get-'(['Result-Code', 'Inband-Security-Id'], CEA), #diameter_caps{origin_host = {OH, DH}} = Caps @@ -664,10 +692,10 @@ build_answer('CER', orelse ?THROW(4003), %% DIAMETER_ELECTION_LOST caps_cb(Caps) of - N -> {cea(CEA, N), [fun open/5, Pkt, - SupportedApps, - Caps, - {accept, hd([_] = IS)}]} + N -> {cea(CEA, N, Dict0), [fun open/5, Pkt, + SupportedApps, + Caps, + {accept, hd([_] = IS)}]} catch ?FAILURE(Reason) -> rejected(Reason, {'CER', Reason, Caps, Pkt}, S) @@ -684,25 +712,25 @@ build_answer(Type, RC = rc(H, Es), {answer(Type, RC, Es, S), post(Type, RC, Pkt, S)}. -cea(CEA, ok) -> +cea(CEA, ok, _) -> CEA; -cea(CEA, 2001) -> +cea(CEA, 2001, _) -> CEA; -cea(CEA, RC) -> - CEA#diameter_base_CEA{'Result-Code' = RC}. +cea(CEA, RC, Dict0) -> + Dict0:'#set-'({'Result-Code', RC}, CEA). post('CER' = T, RC, Pkt, S) -> - [fun close/2, {T, caps(S), {RC, Pkt}}]; + {T, caps(S), {RC, Pkt}}; post(_, _, _, _) -> ok. rejected({capabilities_cb, _F, Reason}, T, S) -> rejected(Reason, T, S); -rejected(discard, T, S) -> - close(T, S); +rejected(discard, T, _) -> + close(T); rejected({N, Es}, T, S) -> - {answer('CER', N, Es, S), [fun close/2, T]}; + {answer('CER', N, Es, S), T}; rejected(N, T, S) -> rejected({N, []}, T, S). @@ -728,7 +756,7 @@ is_origin({N, _}) -> orelse N == 'Origin-State-Id'. %% failed_avp/1 - + failed_avp([] = No) -> No; failed_avp(Avps) -> @@ -817,22 +845,23 @@ a('DPR', #diameter_caps{origin_host = {Host, _}, %% recv_CER/2 -recv_CER(CER, #state{service = Svc}) -> - {ok, T} = diameter_capx:recv_CER(CER, Svc), +recv_CER(CER, #state{service = Svc, dictionary = Dict}) -> + {ok, T} = diameter_capx:recv_CER(CER, Svc, Dict), T. %% handle_CEA/1 handle_CEA(#diameter_packet{bin = Bin} = Pkt, - #state{service = #diameter_service{capabilities = LCaps}} + #state{dictionary = Dict0, + service = #diameter_service{capabilities = LCaps}} = S) when is_binary(Bin) -> ?LOG(recv, 'CEA'), #diameter_packet{msg = CEA} = DPkt - = diameter_codec:decode(?BASE, Pkt), + = diameter_codec:decode(Dict0, Pkt), {SApps, IS, RCaps} = recv_CEA(DPkt, S), @@ -840,8 +869,7 @@ handle_CEA(#diameter_packet{bin = Bin} = Caps = capz(LCaps, RCaps), - #diameter_base_CEA{'Result-Code' = RC} - = CEA, + RC = Dict0:'#get-'('Result-Code', CEA), %% Ensure that we don't already have a connection to the peer in %% question. This isn't the peer election of 3588 except in the @@ -862,7 +890,7 @@ handle_CEA(#diameter_packet{bin = Bin} of _ -> open(DPkt, SApps, Caps, {connect, hd([_] = IS)}, S) catch - ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt}, S) + ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt}) end. %% Check more than the result code since the peer could send success %% regardless. If not 2001 then a peer_up callback could do anything @@ -877,12 +905,13 @@ recv_CEA(#diameter_packet{header = #diameter_header{version is_error = false}, msg = CEA, errors = []}, - #state{service = Svc}) -> - {ok, T} = diameter_capx:recv_CEA(CEA, Svc), + #state{service = Svc, + dictionary = Dict}) -> + {ok, T} = diameter_capx:recv_CEA(CEA, Svc, Dict), T; recv_CEA(Pkt, S) -> - close({'CEA', caps(S), Pkt}, S). + close({'CEA', caps(S), Pkt}). caps(#diameter_service{capabilities = Caps}) -> Caps; @@ -935,14 +964,14 @@ open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid, %% We've advertised TLS support: tell the transport the result %% and expect a reply when the handshake is complete. -tls_ack(true, Caps, Type, IS, #state{transport = TPid} = S) -> +tls_ack(true, Caps, Type, IS, #state{transport = TPid}) -> Ref = make_ref(), TPid ! {diameter, {tls, Ref, Type, IS == ?TLS}}, receive {diameter, {tls, Ref}} -> ok; {'DOWN', _, process, TPid, Reason} -> - close({tls_ack, Reason, Caps}, S) + close({tls_ack, Reason, Caps}) end; %% Or not. Don't send anything to the transport so that transports @@ -955,25 +984,11 @@ capz(#diameter_caps{} = L, #diameter_caps{} = R) -> = list_to_tuple([diameter_caps | lists:zip(tl(tuple_to_list(L)), tl(tuple_to_list(R)))]). -%% close/2 +%% close/1 -%% Tell the watchdog that our death isn't due to transport failure. -close(Reason, #state{parent = Pid}) -> - close_wd(Reason, Pid), +close(Reason) -> throw({?MODULE, close, Reason}). -%% close_wd/2 - -%% Ensure the watchdog dies if DPR has been sent ... -close_wd(_, #state{dpr = false}) -> - ok; -close_wd(Reason, #state{parent = Pid}) -> - close_wd(Reason, Pid); - -%% ... or otherwise -close_wd(Reason, Pid) -> - Pid ! {close, self(), Reason}. - %% dwa/1 dwa(#diameter_caps{origin_host = OH, @@ -1035,13 +1050,14 @@ dpr([CB|Rest], [Reason | _] = Args, S) -> diameter_lib:error_report(failure, No), {stop, No} end; - + dpr([], [Reason | _], S) -> send_dpr(Reason, [], S). -record(opts, {cause, timeout = ?DPA_TIMEOUT}). send_dpr(Reason, Opts, #state{transport = TPid, + dictionary = Dict, service = #diameter_service{capabilities = Caps}} = S) -> #opts{cause = Cause, timeout = Tmo} @@ -1061,7 +1077,8 @@ send_dpr(Reason, Opts, #state{transport = TPid, = Pkt = encode(['DPR', {'Origin-Host', OH}, {'Origin-Realm', OR}, - {'Disconnect-Cause', Cause}]), + {'Disconnect-Cause', Cause}], + Dict), send(TPid, Pkt), dpa_timer(Tmo), ?LOG(send, 'DPR'), diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl index 619b12ecad..3197c1aee1 100644 --- a/lib/diameter/src/base/diameter_reg.erl +++ b/lib/diameter/src/base/diameter_reg.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 @@ -138,7 +138,7 @@ del(T) -> %% associations removed.) %% =========================================================================== --spec match(tuple()) +-spec match(any()) -> [{term(), pid()}]. match(Pat) -> @@ -208,10 +208,6 @@ init(_) -> %% # handle_call/3 %% ---------------------------------------------------------- -handle_call(Req, From, S) - when not is_record(S, state) -> - handle_call(Req, From, upgrade(S)); - handle_call({add, Fun, Key, Pid}, _, S) -> B = Fun(?TABLE, {Key, Pid}), monitor(B andalso no_monitor(Pid), Pid), @@ -281,9 +277,6 @@ code_change(_OldVsn, State, _Extra) -> %% =========================================================================== -upgrade(S) -> - #state{} = list_to_tuple(tuple_to_list(S) ++ [[]]). - monitor(true, Pid) -> ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid))); monitor(false, _) -> diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index d2a416166f..e4d1c60727 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -24,33 +24,38 @@ -module(diameter_service). -behaviour(gen_server). +%% towards diameter_service_sup +-export([start_link/1]). + +%% towards diameter +-export([subscribe/1, + unsubscribe/1, + services/0, + info/2]). + +%% towards diameter_config -export([start/1, stop/1, start_transport/2, - stop_transport/2, - info/2, - call/4]). + stop_transport/2]). -%% towards diameter_watchdog --export([receive_message/3]). +%% towards diameter_peer +-export([notify/2]). -%% service supervisor --export([start_link/1]). +%% towards diameter_traffic +-export([find_incoming_app/4, + pick_peer/3]). --export([subscribe/1, - unsubscribe/1, +%% test/debug +-export([services/1, subscriptions/1, subscriptions/0, - services/0, - services/1, - whois/1]). - -%% test/debug --export([call_module/3, + call_module/3, + whois/1, state/1, uptime/1]). -%%% gen_server callbacks +%% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, @@ -58,21 +63,10 @@ terminate/2, code_change/3]). -%% Other callbacks. --export([send/1]). - -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). -%% The states mirrored by peer_up/peer_down callbacks. --define(STATE_UP, up). --define(STATE_DOWN, down). - --type op_state() :: ?STATE_UP - | ?STATE_DOWN. - -%% The RFC 3539 watchdog states that are now maintained, albeit -%% along with the old up/down. okay = up, else down. +%% RFC 3539 watchdog states. -define(WD_INITIAL, initial). -define(WD_OKAY, okay). -define(WD_SUSPECT, suspect). @@ -86,11 +80,8 @@ | ?WD_REOPEN. -define(DEFAULT_TC, 30000). %% RFC 3588 ch 2.1 --define(DEFAULT_TIMEOUT, 5000). %% for outgoing requests -define(RESTART_TC, 1000). %% if restart was this recent --define(RELAY, ?DIAMETER_DICT_RELAY). - %% Used to be able to swap this with anything else dict-like but now %% rely on the fact that a service's #state{} record does not change %% in storing in it ?STATE table and not always going through the @@ -98,10 +89,6 @@ %% a ?Dict don't change the handle to it. -define(Dict, diameter_dict). -%% Table containing outgoing requests for which a reply has yet to be -%% received. --define(REQUEST_TABLE, diameter_request). - %% Maintains state in a table. In contrast to previously, a service's %% stat is not constant and is accessed outside of the service %% process. @@ -115,83 +102,58 @@ %% Workaround for dialyzer's lack of understanding of match specs. -type match(T) - :: T | '_' | '$1' | '$2' | '$3' | '$4'. - -%% State of service gen_server. + :: T | '_' | '$1' | '$2'. + +%% State of service gen_server. Note that the state term itself +%% doesn't change, which is relevant for the stateless application +%% callbacks since the state is retrieved from ?STATE_TABLE from +%% outside the service process. The pid in the service record is used +%% to determine whether or not we need to call the process for a +%% pick_peer callback in the statefull case. -record(state, {id = now(), - service_name, %% as passed to start_service/2, key in ?STATE_TABLE + service_name :: diameter:service_name(), %% key in ?STATE_TABLE service :: #diameter_service{}, - peerT = ets_new(peers) :: ets:tid(),%% #peer{} at start_fsm - connT = ets_new(conns) :: ets:tid(),%% #conn{} at connection_up/reopen - shared_peers = ?Dict:new(), %% Alias -> [{TPid, Caps}, ...] - local_peers = ?Dict:new(), %% Alias -> [{TPid, Caps}, ...] + watchdogT = ets_new(watchdogs) %% #watchdog{} at start + :: ets:tid(), + peerT = ets_new(peers) %% #peer{pid = TPid} at okay/reopen + :: ets:tid(), + shared_peers = ?Dict:new() %% Alias -> [{TPid, Caps}, ...] + :: ets:tid(), + local_peers = ?Dict:new() %% Alias -> [{TPid, Caps}, ...] + :: ets:tid(), 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? -%% shared_peers reflects the peers broadcast from remote nodes. Note -%% that the state term itself doesn't change, which is relevant for -%% the stateless application callbacks since the state is retrieved -%% from ?STATE_TABLE from outside the service process. The pid in the -%% service record is used to determine whether or not we need to call -%% the process for a pick_peer callback. - -%% Record representing a watchdog process as implemented by -%% diameter_watchdog. The term "peer" here is historical, made -%% especially confusing by the fact that a peer_ref() in the -%% documentation is the key of a #conn{} record, not a #peer{} record. -%% The name is also unfortunate given the meaning of peer in the -%% Diameter sense. --record(peer, + | {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 +%% diameter_watchdog. +-record(watchdog, {pid :: match(pid()), type :: match(connect | accept), ref :: match(reference()), %% key into diameter_config options :: match([diameter:transport_opt()]),%% from start_transport - op_state = {?STATE_DOWN, ?WD_INITIAL} - :: match(op_state() | {op_state(), wd_state()}), + state = ?WD_INITIAL :: match(wd_state()), started = now(), %% at process start - conn = false :: match(boolean() | pid())}). - %% true at accepted, pid() at connection_up or reopen - -%% Record representing a peer process as implemented by -%% diameter_peer_fsm. The term "conn" is historical. Despite the name -%% here, comments refer to watchdog and peer processes, that are keys -%% in #peer{} and #conn{} records respectively. To add to the -%% confusion, a #request.transport is a peer process = key in a -%% #conn{} record. The actual transport process (that the peer process -%% knows about and that has a transport connection) isn't seen here. --record(conn, + peer = false :: match(boolean() | pid())}). + %% true at accepted, pid() at okay/reopen + +%% Record representing an Peer State Machine processes implemented by +%% diameter_peer_fsm. +-record(peer, {pid :: pid(), apps :: [{0..16#FFFFFFFF, diameter:app_alias()}], %% {Id, Alias} caps :: #diameter_caps{}, started = now(), %% at process start - peer :: pid()}). %% key into peerT - -%% Record stored in diameter_request for each outgoing request. --record(request, - {from, %% arg 2 of handle_call/3 - handler :: match(pid()), %% request process - transport :: match(pid()), %% peer process - caps :: match(#diameter_caps{}), - app :: match(diameter:app_alias()),%% #diameter_app.alias - dictionary :: match(module()), %% #diameter_app.dictionary - module :: match([module() | list()]), %% #diameter_app.module - filter :: match(diameter:peer_filter()), - packet :: match(#diameter_packet{})}). - -%% Record call/4 options are parsed into. --record(options, - {filter = none :: diameter:peer_filter(), - extra = [] :: list(), - timeout = ?DEFAULT_TIMEOUT :: 0..16#FFFFFFFF, - detach = false :: boolean()}). - -%%% --------------------------------------------------------------------------- -%%% # start(SvcName) -%%% --------------------------------------------------------------------------- + watchdog :: pid()}). %% key into watchdogT + +%% --------------------------------------------------------------------------- +%% # start/1 +%% --------------------------------------------------------------------------- start(SvcName) -> diameter_service_sup:start_child(SvcName). @@ -202,9 +164,9 @@ start_link(SvcName) -> %% Put the arbitrary term SvcName in a list in case we ever want to %% send more than this and need to distinguish old from new. -%%% --------------------------------------------------------------------------- -%%% # stop(SvcName) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # stop/1 +%% --------------------------------------------------------------------------- stop(SvcName) -> case whois(SvcName) of @@ -220,180 +182,43 @@ stop(ok, Pid) -> stop(No, _) -> No. -%%% --------------------------------------------------------------------------- -%%% # start_transport(SvcName, {Ref, Type, Opts}) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # start_transport/3 +%% --------------------------------------------------------------------------- -start_transport(SvcName, {_,_,_} = T) -> +start_transport(SvcName, {_Ref, _Type, _Opts} = T) -> call_service_by_name(SvcName, {start, T}). -%%% --------------------------------------------------------------------------- -%%% # stop_transport(SvcName, Refs) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # stop_transport/2 +%% --------------------------------------------------------------------------- stop_transport(_, []) -> ok; stop_transport(SvcName, [_|_] = Refs) -> call_service_by_name(SvcName, {stop, Refs}). -%%% --------------------------------------------------------------------------- -%%% # info(SvcName, Item) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # info/2 +%% --------------------------------------------------------------------------- info(SvcName, Item) -> - case find_state(SvcName) of - #state{} = S -> + case lookup_state(SvcName) of + [#state{} = S] -> service_info(Item, S); - false -> + [] -> undefined end. -%%% --------------------------------------------------------------------------- -%%% # receive_message(TPid, Pkt, MessageData) -%%% --------------------------------------------------------------------------- - -%% Handle an incoming Diameter message in the watchdog process. This -%% used to come through the service process but this avoids that -%% becoming a bottleneck. - -receive_message(TPid, Pkt, T) - when is_pid(TPid) -> - #diameter_packet{header = #diameter_header{is_request = R}} = Pkt, - recv(R, (not R) andalso lookup_request(Pkt, TPid), TPid, Pkt, T). +%% lookup_state/1 -%% Incoming request ... -recv(true, false, TPid, Pkt, T) -> - try - spawn(fun() -> recv_request(TPid, Pkt, T) end) - catch - error: system_limit = E -> %% discard - ?LOG({error, E}, now()) - end; - -%% ... answer to known request ... -recv(false, #request{from = {_, Ref}, handler = Pid} = Req, _, Pkt, _) -> - Pid ! {answer, Ref, Req, Pkt}; -%% Note that failover could have happened prior to this message being -%% received and triggering failback. That is, both a failover message -%% and answer may be on their way to the handler process. In the worst -%% case the request process gets notification of the failover and -%% sends to the alternate peer before an answer arrives, so it's -%% always the case that we can receive more than one answer after -%% failover. The first answer received by the request process wins, -%% any others are discarded. - -%% ... or not. -recv(false, false, _, _, _) -> - ok. - -%%% --------------------------------------------------------------------------- -%%% # call(SvcName, App, Msg, Options) -%%% --------------------------------------------------------------------------- - -call(SvcName, App, Msg, Options) - when is_list(Options) -> - Rec = make_options(Options), - Ref = make_ref(), - Caller = {self(), Ref}, - Fun = fun() -> exit({Ref, call(SvcName, App, Msg, Rec, Caller)}) end, - try spawn_monitor(Fun) of - {_, MRef} -> - recv(MRef, Ref, Rec#options.detach, false) - catch - error: system_limit = E -> - {error, E} - end. - -%% Don't rely on gen_server:call/3 for the timeout handling since it -%% makes no guarantees about not leaving a reply message in the -%% mailbox if we catch its exit at timeout. It currently *can* do so, -%% which is also undocumented. - -recv(MRef, _, true, true) -> - erlang:demonitor(MRef, [flush]), - ok; - -recv(MRef, Ref, Detach, Sent) -> - receive - Ref -> %% send has been attempted - recv(MRef, Ref, Detach, true); - {'DOWN', MRef, process, _, Reason} -> - call_rc(Reason, Ref, Sent) - end. - -%% call/5 has returned ... -call_rc({Ref, Ans}, Ref, _) -> - Ans; - -%% ... or not. In this case failure/encode are documented. -call_rc(_, _, Sent) -> - {error, choose(Sent, failure, encode)}. - -%% call/5 -%% -%% In the process spawned for the outgoing request. - -call(SvcName, App, Msg, Opts, Caller) -> - c(find_state(SvcName), App, Msg, Opts, Caller). - -c(#state{service_name = Svc, options = [{_, Mask} | _]} = S, - App, - Msg, - Opts, - Caller) -> - case find_transport(App, Msg, Opts, S) of - {_,_,_} = T -> - send_request(T, Mask, Msg, Opts, Caller, Svc); - false -> - {error, no_connection}; - {error, _} = No -> - No - end; - -c(false, _, _, _, _) -> - {error, no_service}. - -%% find_state/1 - -find_state(SvcName) -> - fs(ets:lookup(?STATE_TABLE, SvcName)). - -fs([#state{} = S]) -> - S; - -fs([]) -> - false. - -%% make_options/1 - -make_options(Options) -> - lists:foldl(fun mo/2, #options{}, Options). - -mo({timeout, T}, Rec) - when is_integer(T), 0 =< T -> - Rec#options{timeout = T}; - -mo({filter, F}, #options{filter = none} = Rec) -> - Rec#options{filter = F}; -mo({filter, F}, #options{filter = {all, Fs}} = Rec) -> - Rec#options{filter = {all, [F | Fs]}}; -mo({filter, F}, #options{filter = F0} = Rec) -> - Rec#options{filter = {all, [F0, F]}}; - -mo({extra, L}, #options{extra = X} = Rec) - when is_list(L) -> - Rec#options{extra = X ++ L}; +lookup_state(SvcName) -> + ets:lookup(?STATE_TABLE, SvcName). -mo(detach, Rec) -> - Rec#options{detach = true}; - -mo(T, _) -> - ?ERROR({invalid_option, T}). - -%%% --------------------------------------------------------------------------- -%%% # subscribe(SvcName) -%%% # unsubscribe(SvcName) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # subscribe/1 +%% # unsubscribe/1 +%% --------------------------------------------------------------------------- subscribe(SvcName) -> diameter_reg:add({?MODULE, subscriber, SvcName}). @@ -410,9 +235,9 @@ subscriptions() -> pmap(Props) -> lists:map(fun({{?MODULE, _, Name}, Pid}) -> {Name, Pid} end, Props). -%%% --------------------------------------------------------------------------- -%%% # services(Pattern) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # services/1 +%% --------------------------------------------------------------------------- services(Pat) -> pmap(diameter_reg:match({?MODULE, service, Pat})). @@ -428,6 +253,86 @@ whois(SvcName) -> undefined end. +%% --------------------------------------------------------------------------- +%% # pick_peer/3 +%% --------------------------------------------------------------------------- + +-spec pick_peer(SvcName, AppOrAlias, Opts) + -> {{TPid, Caps, App}, Mask} + | false + | {error, term()} + when SvcName :: diameter:service_name(), + AppOrAlias :: {alias, diameter:app_alias()} | #diameter_app{}, + Opts :: tuple(), + TPid :: pid(), + Caps :: #diameter_caps{}, + App :: #diameter_app{}, + Mask :: diameter:sequence(). + +pick_peer(SvcName, App, Opts) -> + pick(lookup_state(SvcName), App, Opts). + +pick([], _, _) -> + {error, no_service}; + +pick([S], App, Opts) -> + pick(S, App, Opts); + +pick(#state{service = #diameter_service{applications = Apps}} + = S, + {alias, Alias}, + Opts) -> %% initial call from diameter:call/4 + pick(S, find_outgoing_app(Alias, Apps), Opts); + +pick(_, false, _) -> + false; + +pick(#state{options = [{_, Mask} | _]} + = S, + #diameter_app{module = ModX, dictionary = Dict} + = App0, + {DestF, Filter, Xtra}) -> + App = App0#diameter_app{module = ModX ++ Xtra}, + [_,_] = RealmAndHost = diameter_lib:eval([DestF, Dict]), + case pick_peer(App, RealmAndHost, Filter, S) of + {TPid, Caps} -> + {{TPid, Caps, App}, Mask}; + false = No -> + No + end. + +%% --------------------------------------------------------------------------- +%% # find_incoming_app/4 +%% --------------------------------------------------------------------------- + +-spec find_incoming_app(PeerT, TPid, Id, Apps) + -> {#diameter_app{}, #diameter_caps{}} %% connection and suitable app + | #diameter_caps{} %% connection but no suitable app + | false %% no connection + when PeerT :: ets:tid(), + TPid :: pid(), + Id :: non_neg_integer(), + Apps :: [#diameter_app{}]. + +find_incoming_app(PeerT, TPid, Id, Apps) -> + try ets:lookup(PeerT, TPid) of + [#peer{} = P] -> + find_incoming_app(P, Id, Apps); + [] -> %% transport has gone down + false + catch + error: badarg -> %% service has gone down (and taken table with it) + false + end. + +%% --------------------------------------------------------------------------- +%% # notify/2 +%% --------------------------------------------------------------------------- + +notify(SvcName, Msg) -> + Pid = whois(SvcName), + is_pid(Pid) andalso (Pid ! Msg). + %% =========================================================================== %% =========================================================================== @@ -442,9 +347,9 @@ uptime(Svc) -> call_module(Service, AppMod, Request) -> call_service(Service, {call_module, AppMod, Request}). -%%% --------------------------------------------------------------------------- -%%% # init([SvcName]) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # init/1 +%% --------------------------------------------------------------------------- init([SvcName]) -> process_flag(trap_exit, true), %% ensure terminate(shutdown, _) @@ -455,9 +360,9 @@ i(SvcName, true) -> i(_, false) -> {stop, {shutdown, already_started}}. -%%% --------------------------------------------------------------------------- -%%% # handle_call(Req, From, State) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # handle_call/3 +%% --------------------------------------------------------------------------- handle_call(state, _, S) -> {reply, S, S}; @@ -493,17 +398,17 @@ handle_call(Req, From, S) -> unexpected(handle_call, [Req, From], S), {reply, nok, S}. -%%% --------------------------------------------------------------------------- -%%% # handle_cast(Req, State) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # handle_cast/2 +%% --------------------------------------------------------------------------- handle_cast(Req, S) -> unexpected(handle_cast, [Req], S), {noreply, S}. -%%% --------------------------------------------------------------------------- -%%% # handle_info(Req, State) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # handle_info/2 +%% --------------------------------------------------------------------------- handle_info(T, #state{} = S) -> case transition(T,S) of @@ -520,67 +425,39 @@ transition({accepted, Pid, TPid}, S) -> accepted(Pid, TPid, S), ok; -%% Peer process has a new open connection. -transition({connection_up, Pid, T}, S) -> - connection_up(Pid, T, S), - ok; - -%% Watchdog has a new connection that will be opened after DW[RA] -%% exchange. This message was added long after connection_up, to -%% communicate the information as soon as it's available. Leave -%% connection_up as is it for now, duplicated information and all. -transition({reopen, Pid, T}, S) -> - reopen(Pid, T, S), - ok; - -%% Watchdog has left state OKAY. -transition({connection_down, Pid}, S) -> - connection_down(Pid, S), - ok; - -%% Watchdog has returned to state OKAY. -transition({connection_up, Pid}, S) -> - connection_up(Pid, S), - ok; - -%% Accepting transport has lost connectivity. -transition({close, Pid}, S) -> - close(Pid, S), - ok; - %% Connecting transport is being restarted by watchdog. transition({reconnect, Pid}, S) -> reconnect(Pid, S), ok; -%% Watchdog is sending notification of a state transition. Note that -%% the connection_up/down messages pre-date this message and are still -%% used. A watchdog message will follow these and communicate the same -%% state as was set in handling connection_up/down. -transition({watchdog, Pid, {TPid, From, To}}, #state{service_name = SvcName, - peerT = PeerT}) -> - #peer{ref = Ref, type = T, options = Opts, op_state = {OS,_}} - = P - = fetch(PeerT, Pid), - insert(PeerT, P#peer{op_state = {OS, To}}), +%% Watchdog is sending notification of transport death. +transition({close, Pid, Reason}, #state{service_name = SvcName, + watchdogT = WatchdogT}) -> + #watchdog{state = WS, + ref = Ref, + type = Type, + options = Opts} + = fetch(WatchdogT, Pid), + WS /= ?WD_OKAY + andalso + send_event(SvcName, {closed, Ref, Reason, {type(Type), Opts}}), + ok; + +%% Watchdog is sending notification of a state transition. +transition({watchdog, Pid, {[TPid | Data], From, To}}, + #state{service_name = SvcName, + watchdogT = WatchdogT} + = S) -> + #watchdog{ref = Ref, type = T, options = Opts} + = Wd + = fetch(WatchdogT, Pid), + watchdog(TPid, Data, From, To, Wd, S), send_event(SvcName, {watchdog, Ref, TPid, {From, To}, {T, Opts}}), ok; -%% Death of a watchdog process (#peer.pid) results in the removal of -%% it's peer and any associated conn record when 'DOWN' is received -%% (after this) but the states will be {?STATE_UP, ?WD_DOWN} for a -%% short time. (No real problem since ?WD_* is only used in -%% service_info.) We set ?WD_OKAY as a consequence of connection_up -%% since we know a watchdog is coming. We can't set anything at -%% connection_down since we don't know if the subsequent watchdog -%% message will be ?WD_DOWN or ?WD_SUSPECT. We don't (yet) set -%% ?STATE_* as a consequence of a watchdog message since this requires -%% changing some of the matching on ?STATE_*. -%% -%% Death of a peer process process (#conn.pid, #peer.conn) results in -%% connection_down followed by watchdog ?WD_DOWN. The latter doesn't -%% result in the conn record being deleted since 'DOWN' from death of -%% its watchdog doesn't (yet) deal with the record having been -%% removed. +%% Death of a watchdog process (#watchdog.pid) results in the removal of +%% it's peer and any associated conn record when 'DOWN' is received. +%% Death of a peer process process (#peer.pid, #watchdog.peer) results in +%% ?WD_DOWN. %% Monitor process has died. Just die with a reason that tells %% diameter_config about the happening. If a cleaner shutdown is @@ -589,9 +466,9 @@ transition({'DOWN', MRef, process, _, Reason}, #state{monitor = MRef}) -> {stop, {monitor, Reason}}; %% Local watchdog process has died. -transition({'DOWN', _, process, Pid, Reason}, S) +transition({'DOWN', _, process, Pid, _Reason}, S) when node(Pid) == node() -> - peer_down(Pid, Reason, S), + watchdog_down(Pid, S), ok; %% Remote service wants to know about shared peers. @@ -614,20 +491,13 @@ transition({tc_timeout, T}, S) -> tc_timeout(T, S), ok; -%% Request process is telling us it may have missed a failover message -%% after a transport went down and the service process looked up -%% outstanding requests. -transition({failover, TRef, Seqs}, S) -> - failover(TRef, Seqs, S), - ok; - transition(Req, S) -> unexpected(handle_info, [Req], S), ok. -%%% --------------------------------------------------------------------------- -%%% # terminate(Reason, State) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # terminate/2 +%% --------------------------------------------------------------------------- terminate(Reason, #state{service_name = Name} = S) -> send_event(Name, stop), @@ -635,9 +505,9 @@ terminate(Reason, #state{service_name = Name} = S) -> shutdown == Reason %% application shutdown andalso shutdown(application, S). -%%% --------------------------------------------------------------------------- -%%% # code_change(FromVsn, State, Extra) -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # code_change/3 +%% --------------------------------------------------------------------------- code_change(FromVsn, #state{service_name = SvcName, @@ -663,30 +533,20 @@ code_change(FromVsn, SvcName, Extra, #diameter_app{alias = Alias} = A) -> unexpected(F, A, #state{service_name = Name}) -> ?UNEXPECTED(F, A ++ [Name]). -cb([_|_] = M, F, A) -> - eval(M, F, A); -cb(Rec, F, A) -> - {_, M} = app(Rec), +cb(#diameter_app{module = [_|_] = M}, F, A) -> eval(M, F, A). -app(#request{app = A, module = M}) -> - {A,M}; -app(#diameter_app{alias = A, module = M}) -> - {A,M}. - eval([M|X], F, A) -> apply(M, F, A ++ X). %% Callback with state. -state_cb(#diameter_app{mutable = false, init_state = S}, {ModX, F, A}) -> +state_cb(#diameter_app{module = ModX, mutable = false, init_state = S}, + pick_peer = F, + A) -> eval(ModX, F, A ++ [S]); -state_cb(#diameter_app{mutable = true, alias = Alias}, {_,_,_} = MFA) -> - state_cb(MFA, Alias); - -state_cb({ModX,F,A}, Alias) - when is_list(ModX) -> +state_cb(#diameter_app{module = ModX, alias = Alias}, F, A) -> eval(ModX, F, A ++ [mod_state(Alias)]). choose(true, X, _) -> X; @@ -712,57 +572,38 @@ mod_state(Alias) -> mod_state(Alias, ModS) -> put({?MODULE, mod_state, Alias}, ModS). -%%% --------------------------------------------------------------------------- -%%% # shutdown/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # shutdown/2 +%% --------------------------------------------------------------------------- -%% remove_transport: ask watchdogs to terminate their transport. -shutdown(Refs, #state{peerT = PeerT}) +%% remove_transport +shutdown(Refs, #state{watchdogT = WatchdogT}) when is_list(Refs) -> - ets:foldl(fun(P,ok) -> sp(P, Refs), ok end, ok, PeerT); - -%% application/service shutdown: ask transports to terminate themselves. -shutdown(Reason, #state{peerT = PeerT}) -> - %% A transport might not be alive to receive the shutdown request - %% but give those that are a chance to shutdown gracefully. - shutdown(conn, Reason, PeerT), - %% Kill the watchdogs explicitly in case there was no transport. - shutdown(peer, Reason, PeerT). - -%% sp/2 + ets:foldl(fun(P,ok) -> st(P, Refs), ok end, ok, WatchdogT); -sp(#peer{ref = Ref, pid = Pid}, Refs) -> - lists:member(Ref, Refs) - andalso (Pid ! {shutdown, self()}). %% 'DOWN' cleans up - -%% shutdown/3 - -shutdown(Who, Reason, T) -> - diameter_lib:wait(ets:foldl(fun(X,A) -> shutdown(Who, X, Reason, A) end, +%% application/service shutdown +shutdown(Reason, #state{watchdogT = WatchdogT}) + when Reason == application; + Reason == service -> + diameter_lib:wait(ets:foldl(fun(P,A) -> st(P, Reason, A) end, [], - T)). + WatchdogT)). -shutdown(conn = Who, #peer{op_state = {OS,_}} = P, Reason, Acc) -> - shutdown(Who, P#peer{op_state = OS}, Reason, Acc); +%% st/2 -shutdown(conn, - #peer{pid = Pid, op_state = ?STATE_UP, conn = TPid}, - Reason, - Acc) -> - TPid ! {shutdown, Pid, Reason}, - [TPid | Acc]; +st(#watchdog{ref = Ref, pid = Pid}, Refs) -> + lists:member(Ref, Refs) + andalso (Pid ! {shutdown, self(), transport}). %% 'DOWN' cleans up -shutdown(peer, #peer{pid = Pid}, _Reason, Acc) - when is_pid(Pid) -> - exit(Pid, shutdown), - [Pid | Acc]; +%% st/3 -shutdown(_, #peer{}, _, Acc) -> - Acc. +st(#watchdog{pid = Pid}, Reason, Acc) -> + Pid ! {shutdown, self(), Reason}, + [Pid | Acc]. -%%% --------------------------------------------------------------------------- -%%% # call_service/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # call_service/2 +%% --------------------------------------------------------------------------- call_service(Pid, Req) when is_pid(Pid) -> @@ -785,11 +626,9 @@ cs(Pid, Req) cs(undefined, _) -> {error, no_service}. -%%% --------------------------------------------------------------------------- -%%% # i/1 -%%% -%%% Output: #state{} -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # i/1 +%% --------------------------------------------------------------------------- %% Intialize the state of a service gen_server. @@ -842,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}) -> @@ -859,9 +696,40 @@ get_value(Key, Vs) -> {_, V} = lists:keyfind(Key, 1, Vs), V. -%%% --------------------------------------------------------------------------- -%%% # start/3 -%%% --------------------------------------------------------------------------- +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 +%% --------------------------------------------------------------------------- %% If the initial start/3 at service/transport start succeeds then %% subsequent calls to start/4 on the same service will also succeed @@ -891,22 +759,28 @@ type(connect = T) -> T. %% start/4 -start(Ref, Type, Opts, #state{peerT = PeerT, - connT = ConnT, +start(Ref, Type, Opts, #state{watchdogT = WatchdogT, + peerT = PeerT, options = SvcOpts, service_name = SvcName, - service = Svc}) + service = Svc0}) when Type == connect; Type == accept -> - Pid = s(Type, Ref, {ConnT, + #diameter_service{applications = Apps} + = Svc + = merge_service(Opts, Svc0), + {_,_} = Mask = proplists:get_value(sequence, SvcOpts), + Pid = s(Type, Ref, {diameter_traffic:make_recvdata([SvcName, + PeerT, + Apps, + Mask]), Opts, - SvcName, SvcOpts, - merge_service(Opts, Svc)}), - insert(PeerT, #peer{pid = Pid, - type = Type, - ref = Ref, - options = Opts}), + Svc}), + insert(WatchdogT, #watchdog{pid = Pid, + type = Type, + ref = Ref, + options = Opts}), Pid. %% Note that the service record passed into the watchdog is the merged @@ -949,100 +823,115 @@ ms({capabilities, Opts}, #diameter_service{capabilities = Caps0} = Svc) ms(_, Svc) -> Svc. -%%% --------------------------------------------------------------------------- -%%% # accepted/3 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # accepted/3 +%% --------------------------------------------------------------------------- -accepted(Pid, _TPid, #state{peerT = PeerT} = S) -> - #peer{ref = Ref, type = accept = T, conn = false, options = Opts} - = P - = fetch(PeerT, Pid), - insert(PeerT, P#peer{conn = true}), %% mark replacement as started - start(Ref, T, Opts, S). %% start new watchdog +accepted(Pid, _TPid, #state{watchdogT = WatchdogT} = S) -> + #watchdog{ref = Ref, type = accept = T, peer = false, options = Opts} + = Wd + = fetch(WatchdogT, Pid), + insert(WatchdogT, Wd#watchdog{peer = true}),%% mark replacement as started + start(Ref, T, Opts, S). %% start new watchdog fetch(Tid, Key) -> [T] = ets:lookup(Tid, Key), - case T of - #peer{op_state = ?STATE_UP} = P -> - P#peer{op_state = {?STATE_UP, ?WD_OKAY}}; - #peer{op_state = ?STATE_DOWN} = P -> - P#peer{op_state = {?STATE_DOWN, ?WD_DOWN}}; - _ -> - T - end. + T. -%%% --------------------------------------------------------------------------- -%%% # connection_up/3 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # watchdog/6 +%% +%% React to a watchdog state transition. +%% --------------------------------------------------------------------------- -%% Watchdog process has reached state OKAY. +%% Watchdog has a new open connection. +watchdog(TPid, [T], _, ?WD_OKAY, Wd, State) -> + connection_up({TPid, T}, Wd, State); -connection_up(Pid, {TPid, {Caps, SApps, Pkt}}, #state{peerT = PeerT, - connT = ConnT} - = S) -> - P = fetch(PeerT, Pid), - C = #conn{pid = TPid, - apps = SApps, - caps = Caps, - peer = Pid}, - - insert(ConnT, C), - connection_up([Pkt], P#peer{conn = TPid}, C, S). - -%%% --------------------------------------------------------------------------- -%%% # reopen/3 -%%% --------------------------------------------------------------------------- - -%% Note that this connection_up/3 rewrites the same #conn{} now -%% written here. Both do so in case reopen has not happened in old -%% code. - -reopen(Pid, {TPid, {Caps, SApps, _Pkt}}, #state{peerT = PeerT, - connT = ConnT}) -> - P = fetch(PeerT, Pid), - C = #conn{pid = TPid, - apps = SApps, - caps = Caps, - peer = Pid}, - - insert(ConnT, C), - #peer{op_state = {?STATE_DOWN, _}} - = P, - insert(PeerT, P#peer{op_state = {?STATE_DOWN, ?WD_REOPEN}, - conn = TPid}). - -%%% --------------------------------------------------------------------------- -%%% # connection_up/2 -%%% --------------------------------------------------------------------------- - -%% Peer process has transitioned back into the open state. Note that there -%% has been no new capabilties exchange in this case. - -connection_up(Pid, #state{peerT = PeerT, - connT = ConnT} - = S) -> - #peer{conn = TPid} = P = fetch(PeerT, Pid), - C = fetch(ConnT, TPid), - connection_up([], P, C, S). +%% Watchdog has a new connection that will be opened after DW[RA] +%% exchange. +watchdog(TPid, [T], _, ?WD_REOPEN, Wd, State) -> + reopen({TPid, T}, Wd, State); + +%% Watchdog has recovered a suspect connection. +watchdog(TPid, [], ?WD_SUSPECT, ?WD_OKAY, Wd, State) -> + #watchdog{peer = TPid} = Wd, %% assert + connection_up(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 has lost its connection. +watchdog(TPid, [], _, ?WD_DOWN = To, Wd, #state{peerT = PeerT} = S) -> + close(Wd, S), + connection_down(Wd, To, S), + ets:delete(PeerT, TPid); + +watchdog(_, [], _, _, _, _) -> + ok. -%% connection_up/4 +%% --------------------------------------------------------------------------- +%% # connection_up/3 +%% --------------------------------------------------------------------------- -connection_up(T, P, C, #state{peerT = PeerT, - local_peers = LDict, - service_name = SvcName, - service - = #diameter_service{applications = Apps}} - = S) -> - #peer{conn = TPid, op_state = {?STATE_DOWN, _}} - = P, - #conn{apps = SApps, caps = Caps} - = C, +%% Watchdog process has reached state OKAY. - insert(PeerT, P#peer{op_state = {?STATE_UP, ?WD_OKAY}}), +connection_up({TPid, {Caps, SupportedApps, Pkt}}, + #watchdog{pid = Pid} + = Wd, + #state{peerT = PeerT} + = S) -> + Pr = #peer{pid = TPid, + apps = SupportedApps, + caps = Caps, + watchdog = Pid}, + insert(PeerT, Pr), + connection_up([Pkt], Wd#watchdog{peer = TPid}, Pr, S). + +%% --------------------------------------------------------------------------- +%% # reopen/3 +%% --------------------------------------------------------------------------- + +reopen({TPid, {Caps, SupportedApps, _Pkt}}, + #watchdog{pid = Pid} + = Wd, + #state{watchdogT = WatchdogT, + peerT = PeerT}) -> + insert(PeerT, #peer{pid = TPid, + apps = SupportedApps, + caps = Caps, + watchdog = Pid}), + insert(WatchdogT, Wd#watchdog{state = ?WD_REOPEN, + peer = TPid}). + +%% --------------------------------------------------------------------------- +%% # connection_up/2 +%% --------------------------------------------------------------------------- + +%% Watchdog has recovered as suspect connection. Note that there has +%% been no new capabilties exchange in this case. + +connection_up(#watchdog{peer = TPid} = Wd, #state{peerT = PeerT} = S) -> + connection_up([], Wd, fetch(PeerT, TPid), S). - request_peer_up(TPid), +%% connection_up/4 + +connection_up(Extra, + #watchdog{peer = TPid} + = Wd, + #peer{apps = SApps, caps = Caps} + = Pr, + #state{watchdogT = WatchdogT, + local_peers = LDict, + service_name = SvcName, + service = #diameter_service{applications = Apps}} + = S) -> + insert(WatchdogT, Wd#watchdog{state = ?WD_OKAY}), + diameter_traffic:peer_up(TPid), insert_local_peer(SApps, {{TPid, Caps}, {SvcName, Apps}}, LDict), - report_status(up, P, C, S, T). + report_status(up, Wd, Pr, S, Extra). insert_local_peer(SApps, T, LDict) -> lists:foldl(fun(A,D) -> ilp(A, T, D) end, LDict, SApps). @@ -1052,13 +941,57 @@ ilp({Id, Alias}, {TC, SA}, LDict) -> ?Dict:append(Alias, TC, LDict). init_conn(Id, Alias, {TPid, _} = TC, {SvcName, Apps}) -> - #diameter_app{module = ModX, - id = Id} %% assert + #diameter_app{id = Id} %% assert + = App = find_app(Alias, Apps), - peer_cb({ModX, peer_up, [SvcName, TC]}, Alias) + peer_cb(App, peer_up, [SvcName, TC]) orelse exit(TPid, kill). %% fake transport failure +%% --------------------------------------------------------------------------- +%% # find_incoming_app/3 +%% --------------------------------------------------------------------------- + +%% No one should be sending the relay identifier. +find_incoming_app(#peer{caps = Caps}, ?APP_ID_RELAY, _) -> + Caps; + +find_incoming_app(Peer, Id, Apps) + when is_integer(Id) -> + find_incoming_app(Peer, [Id, ?APP_ID_RELAY], Apps); + +%% Note that the apps represented in SApps may be a strict subset of +%% those in Apps. +find_incoming_app(#peer{apps = SApps, caps = Caps}, Ids, Apps) -> + case keyfind(Ids, 1, SApps) of + {_Id, Alias} -> + {#diameter_app{} = find_app(Alias, Apps), Caps}; + false -> + Caps + end. + +%% keyfind/3 + +keyfind([], _, _) -> + false; +keyfind([Key | Rest], Pos, L) -> + case lists:keyfind(Key, Pos, L) of + false -> + keyfind(Rest, Pos, L); + T -> + T + end. + +%% find_outgoing_app/2 + +find_outgoing_app(Alias, Apps) -> + case find_app(Alias, Apps) of + #diameter_app{id = ?APP_ID_RELAY} -> + false; + A -> + A + end. + %% find_app/2 find_app(Alias, Apps) -> @@ -1066,53 +999,51 @@ find_app(Alias, Apps) -> %% Don't bring down the service (and all associated connections) %% regardless of what happens. -peer_cb(MFA, Alias) -> - try state_cb(MFA, Alias) of +peer_cb(App, F, A) -> + try state_cb(App, F, A) of ModS -> - mod_state(Alias, ModS), + mod_state(App#diameter_app.alias, ModS), true catch E:R -> - diameter_lib:error_report({failure, {E, R, Alias, ?STACK}}, MFA), + diameter_lib:error_report({failure, {E, R, ?STACK}}, + {App, F, A}), false end. -%%% --------------------------------------------------------------------------- -%%% # connection_down/2 -%%% --------------------------------------------------------------------------- - -%% Watchdog has transitioned out of state OKAY. +%% --------------------------------------------------------------------------- +%% # connection_down/3 +%% --------------------------------------------------------------------------- -connection_down(Pid, #state{peerT = PeerT, - connT = ConnT} - = S) -> - #peer{op_state = {?STATE_UP, WS}, %% assert - conn = TPid} - = P - = fetch(PeerT, Pid), - - C = fetch(ConnT, TPid), - insert(PeerT, P#peer{op_state = {?STATE_DOWN, WS}}), - connection_down(P,C,S). - -%% connection_down/3 - -connection_down(#peer{op_state = {?STATE_DOWN, _}}, _, _) -> - ok; - -connection_down(#peer{conn = TPid, - op_state = {?STATE_UP, _}} - = P, - #conn{caps = Caps, +connection_down(#watchdog{state = ?WD_OKAY, + peer = TPid} + = Wd, + #peer{caps = Caps, apps = SApps} - = C, + = Pr, #state{service_name = SvcName, service = #diameter_service{applications = Apps}, local_peers = LDict} = S) -> - report_status(down, P, C, S, []), + report_status(down, Wd, Pr, S, []), remove_local_peer(SApps, {{TPid, Caps}, {SvcName, Apps}}, LDict), - request_peer_down(TPid, S). + diameter_traffic:peer_down(TPid); + +connection_down(#watchdog{}, #peer{}, _) -> + ok; + +connection_down(#watchdog{state = WS, + peer = TPid} + = Wd, + To, + #state{watchdogT = WatchdogT, + peerT = PeerT} + = S) + when is_atom(To) -> + insert(WatchdogT, Wd#watchdog{state = To}), + ?WD_OKAY == WS + andalso + connection_down(Wd, fetch(PeerT, TPid), S). remove_local_peer(SApps, T, LDict) -> lists:foldl(fun(A,D) -> rlp(A, T, D) end, LDict, SApps). @@ -1123,71 +1054,58 @@ rlp({Id, Alias}, {TC, SA}, LDict) -> ?Dict:store(Alias, lists:delete(TC, L), LDict). down_conn(Id, Alias, TC, {SvcName, Apps}) -> - #diameter_app{module = ModX, - id = Id} %% assert + #diameter_app{id = Id} %% assert + = App = find_app(Alias, Apps), - peer_cb({ModX, peer_down, [SvcName, TC]}, Alias). + peer_cb(App, peer_down, [SvcName, TC]). -%%% --------------------------------------------------------------------------- -%%% # peer_down/3 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # watchdog_down/2 +%% --------------------------------------------------------------------------- %% Watchdog process has died. -peer_down(Pid, Reason, #state{peerT = PeerT} = S) -> - P = fetch(PeerT, Pid), - ets:delete_object(PeerT, P), - closed(Reason, P, S), - restart(P,S), - peer_down(P,S). - -%% Send an event at connection establishment failure. -closed({shutdown, {close, _TPid, Reason}}, - #peer{op_state = {?STATE_DOWN, _}, - ref = Ref, - type = Type, - options = Opts}, - #state{service_name = SvcName}) -> - send_event(SvcName, {closed, Ref, Reason, {type(Type), Opts}}); -closed(_, _, _) -> - ok. +watchdog_down(Pid, #state{watchdogT = WatchdogT} = S) -> + Wd = fetch(WatchdogT, Pid), + ets:delete_object(WatchdogT, Wd), + restart(Wd,S), + wd_down(Wd,S). -%% The watchdog has never reached OKAY ... -peer_down(#peer{conn = B}, _) +%% Watchdog has never reached OKAY ... +wd_down(#watchdog{peer = B}, _) when is_boolean(B) -> ok; %% ... or maybe it has. -peer_down(#peer{conn = TPid} = P, #state{connT = ConnT} = S) -> - #conn{} = C = fetch(ConnT, TPid), - ets:delete_object(ConnT, C), - connection_down(P,C,S). +wd_down(#watchdog{peer = TPid} = Wd, #state{peerT = PeerT} = S) -> + connection_down(Wd, ?WD_DOWN, S), + ets:delete(PeerT, TPid). %% restart/2 -restart(P,S) -> - q_restart(restart(P), S). +restart(Wd, S) -> + q_restart(restart(Wd), S). %% restart/1 %% Always try to reconnect. -restart(#peer{ref = Ref, - type = connect = T, - options = Opts, - started = Time}) -> +restart(#watchdog{ref = Ref, + type = connect = T, + options = Opts, + started = Time}) -> {Time, {Ref, T, Opts}}; %% Transport connection hasn't yet been accepted ... -restart(#peer{ref = Ref, - type = accept = T, - options = Opts, - conn = false, - started = Time}) -> +restart(#watchdog{ref = Ref, + type = accept = T, + options = Opts, + peer = false, + started = Time}) -> {Time, {Ref, T, Opts}}; %% ... or it has: a replacement has already been spawned. -restart(#peer{type = accept}) -> +restart(#watchdog{type = accept}) -> false. %% q_restart/2 @@ -1237,9 +1155,9 @@ tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} tc(false = No, _, _) -> %% removed No. -%%% --------------------------------------------------------------------------- -%%% # close/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # close/2 +%% --------------------------------------------------------------------------- %% The watchdog doesn't start a new fsm in the accept case, it %% simply stays alive until someone tells it to die in order for @@ -1248,14 +1166,13 @@ tc(false = No, _, _) -> %% removed %% the accepting watchdog upon reception of a CER from the previously %% connected peer, or us after reconnect_timer timeout. -close(Pid, #state{service_name = SvcName, - peerT = PeerT}) -> - #peer{pid = Pid, - type = accept, - ref = Ref, - options = Opts} - = fetch(PeerT, Pid), - +close(#watchdog{type = connect}, _) -> + ok; +close(#watchdog{type = accept, + pid = Pid, + ref = Ref, + options = Opts}, + #state{service_name = SvcName}) -> c(Pid, diameter_config:have_transport(SvcName, Ref), Opts). %% Tell watchdog to (maybe) die later ... @@ -1273,21 +1190,21 @@ c(Pid, false, _Opts) -> %% which a new connection attempt is expected of a connecting peer. %% The value should be greater than the peer's Tc + jitter. -%%% --------------------------------------------------------------------------- -%%% # reconnect/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # reconnect/2 +%% --------------------------------------------------------------------------- reconnect(Pid, #state{service_name = SvcName, - peerT = PeerT}) -> - #peer{ref = Ref, - type = connect, - options = Opts} - = fetch(PeerT, Pid), + watchdogT = WatchdogT}) -> + #watchdog{ref = Ref, + type = connect, + options = Opts} + = fetch(WatchdogT, Pid), send_event(SvcName, {reconnect, Ref, Opts}). -%%% --------------------------------------------------------------------------- -%%% # call_module/4 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # call_module/4 +%% --------------------------------------------------------------------------- %% Backwards compatibility and never documented/advertised. May be %% removed. @@ -1309,10 +1226,10 @@ call_module(Mod, Req, From, #state{service {reply, {error, Reason}, S} end. -cm([#diameter_app{module = ModX, alias = Alias}], Req, From, Svc) -> - MFA = {ModX, handle_call, [Req, From, Svc]}, +cm([#diameter_app{alias = Alias} = App], Req, From, Svc) -> + Args = [Req, From, Svc], - try state_cb(MFA, Alias) of + try state_cb(App, handle_call, Args) of {noreply = T, ModS} -> mod_state(Alias, ModS), T; @@ -1320,11 +1237,13 @@ cm([#diameter_app{module = ModX, alias = Alias}], Req, From, Svc) -> mod_state(Alias, ModS), {T, RC}; T -> - diameter_lib:error_report({invalid, T}, MFA), + diameter_lib:error_report({invalid, T}, + {App, handle_call, Args}), invalid catch E: Reason -> - diameter_lib:error_report({failure, {E, Reason, ?STACK}}, MFA), + diameter_lib:error_report({failure, {E, Reason, ?STACK}}, + {App, handle_call, Args}), failure end; @@ -1334,1275 +1253,21 @@ cm([], _, _, _) -> cm([_,_|_], _, _, _) -> multiple. -%%% --------------------------------------------------------------------------- -%%% # send_request/6 -%%% --------------------------------------------------------------------------- - -%% Send an outgoing request in its dedicated process. -%% -%% Note that both encode of the outgoing request and of the received -%% answer happens in this process. It's also this process that replies -%% to the caller. The service process only handles the state-retaining -%% callbacks. -%% -%% The mod field of the #diameter_app{} here includes any extra -%% arguments passed to diameter:call/2. - -send_request({TPid, Caps, App} = T, Mask, Msg, Opts, Caller, SvcName) -> - #diameter_app{module = ModX} - = App, - - Pkt = make_prepare_packet(Mask, Msg), - - send_req(cb(ModX, prepare_request, [Pkt, SvcName, {TPid, Caps}]), - Pkt, - T, - Opts, - Caller, - SvcName, - []). - -send_req({send, P}, Pkt, T, Opts, Caller, SvcName, Fs) -> - send_req(make_request_packet(P, Pkt), T, Opts, Caller, SvcName, Fs); - -send_req({discard, Reason} , _, _, _, _, _, _) -> - {error, Reason}; - -send_req(discard, _, _, _, _, _, _) -> - {error, discarded}; - -send_req({eval_packet, RC, F}, Pkt, T, Opts, Caller, SvcName, Fs) -> - send_req(RC, Pkt, T, Opts, Caller, SvcName, [F|Fs]); - -send_req(E, _, {_, _, App}, _, _, _, _) -> - ?ERROR({invalid_return, prepare_request, App, E}). - -%% make_prepare_packet/2 -%% -%% Turn an outgoing request as passed to call/4 into a diameter_packet -%% record in preparation for a prepare_request callback. - -make_prepare_packet(_, Bin) - when is_binary(Bin) -> - #diameter_packet{header = diameter_codec:decode_header(Bin), - bin = Bin}; - -make_prepare_packet(Mask, #diameter_packet{msg = [#diameter_header{} = Hdr - | Avps]} - = Pkt) -> - Pkt#diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]}; - -make_prepare_packet(Mask, #diameter_packet{header = Hdr} = Pkt) -> - Pkt#diameter_packet{header = make_prepare_header(Mask, Hdr)}; - -make_prepare_packet(Mask, Msg) -> - make_prepare_packet(Mask, #diameter_packet{msg = Msg}). - -%% make_prepare_header/2 - -make_prepare_header(Mask, undefined) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(#diameter_header{end_to_end_id = Seq, - hop_by_hop_id = Seq}); - -make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined, - hop_by_hop_id = undefined} - = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{end_to_end_id = Seq, - hop_by_hop_id = Seq}); - -make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined} = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{end_to_end_id = Seq}); - -make_prepare_header(Mask, #diameter_header{hop_by_hop_id = undefined} = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{hop_by_hop_id = Seq}); - -make_prepare_header(_, Hdr) -> - make_prepare_header(Hdr). - -%% make_prepare_header/1 - -make_prepare_header(#diameter_header{version = undefined} = Hdr) -> - make_prepare_header(Hdr#diameter_header{version = ?DIAMETER_VERSION}); - -make_prepare_header(#diameter_header{} = Hdr) -> - Hdr; - -make_prepare_header(T) -> - ?ERROR({invalid_header, T}). - -%% make_request_packet/2 -%% -%% Reconstruct a diameter_packet from the return value of -%% prepare_request or prepare_retransmit callback. - -make_request_packet(Bin, _) - when is_binary(Bin) -> - make_prepare_packet(false, Bin); - -make_request_packet(#diameter_packet{msg = [#diameter_header{} | _]} - = Pkt, - _) -> - Pkt; - -%% Returning a diameter_packet with no header from a prepare_request -%% or prepare_retransmit callback retains the header passed into it. -%% This is primarily so that the end to end and hop by hop identifiers -%% are retained. -make_request_packet(#diameter_packet{header = Hdr} = Pkt, - #diameter_packet{header = Hdr0}) -> - Pkt#diameter_packet{header = fold_record(Hdr0, Hdr)}; - -make_request_packet(Msg, Pkt) -> - Pkt#diameter_packet{msg = Msg}. - -%% fold_record/2 - -fold_record(undefined, R) -> - R; -fold_record(Rec, R) -> - diameter_lib:fold_tuple(2, Rec, R). - -%% send_req/6 - -send_req(Pkt, {TPid, Caps, App}, Opts, Caller, SvcName, Fs) -> - #diameter_app{alias = Alias, - dictionary = Dict, - module = ModX, - options = [{answer_errors, AE} | _]} - = App, - - EPkt = encode(Dict, Pkt, Fs), - - #options{filter = Filter, - timeout = Timeout} - = Opts, - - Req = #request{packet = Pkt, - from = Caller, - handler = self(), - transport = TPid, - caps = Caps, - app = Alias, - filter = Filter, - dictionary = Dict, - module = ModX}, - - try - TRef = send_request(TPid, EPkt, Req, Timeout), - ack(Caller), - handle_answer(SvcName, AE, recv_answer(Timeout, SvcName, {TRef, Req})) - after - erase_request(EPkt) - end. - -%% Tell caller a send has been attempted. -ack({Pid, Ref}) -> - Pid ! Ref. - -%% recv_answer/3 - -recv_answer(Timeout, - SvcName, - {TRef, #request{from = {_, Ref}, packet = RPkt} = Req} - = T) -> - - %% Matching on TRef below ensures we ignore messages that pertain - %% to a previous transport prior to failover. The answer message - %% includes the #request{} since it's not necessarily Req; that - %% is, from the last peer to which we've transmitted. - - receive - {answer = A, Ref, Rq, Pkt} -> %% Answer from peer - {A, Rq, Pkt}; - {timeout = Reason, TRef, _} -> %% No timely reply - {error, Req, Reason}; - {failover = Reason, TRef, false} -> %% No alternate peer - {error, Req, Reason}; - {failover, TRef, Transport} -> %% Resend to alternate peer - try_retransmit(Timeout, SvcName, Req, Transport); - {failover, TRef} -> %% May have missed failover notification - Seqs = diameter_codec:sequence_numbers(RPkt), - Pid = whois(SvcName), - is_pid(Pid) andalso (Pid ! {failover, TRef, Seqs}), - recv_answer(Timeout, SvcName, T) - end. -%% Note that failover starts a new timer and that expiry of an old -%% timer value is ignored. This means that an answer could be accepted -%% from a peer after timeout in the case of failover. - -try_retransmit(Timeout, SvcName, Req, Transport) -> - try retransmit(Transport, Req, SvcName, Timeout) of - T -> recv_answer(Timeout, SvcName, T) - catch - ?FAILURE(Reason) -> {error, Req, Reason} - end. - -%% handle_error/3 - -handle_error(Req, Reason, SvcName) -> - #request{module = ModX, - packet = Pkt, - transport = TPid, - caps = Caps} - = Req, - cb(ModX, handle_error, [Reason, msg(Pkt), SvcName, {TPid, Caps}]). - -msg(#diameter_packet{msg = undefined, bin = Bin}) -> - Bin; -msg(#diameter_packet{msg = Msg}) -> - Msg. - -%% encode/3 - -encode(Dict, Pkt, Fs) -> - P = encode(Dict, Pkt), - eval_packet(P, Fs), - P. - -%% encode/2 - -%% Note that prepare_request can return a diameter_packet containing -%% header or transport_data. Even allow the returned record to contain -%% an encoded binary. This isn't the usual case but could some in -%% handy, for test at least. (For example, to send garbage.) - -%% The normal case: encode the returned message. -encode(Dict, #diameter_packet{msg = Msg, bin = undefined} = Pkt) -> - D = pick_dictionary([Dict, ?BASE], Msg), - diameter_codec:encode(D, Pkt); - -%% Callback has returned an encoded binary: just send. -encode(_, #diameter_packet{} = Pkt) -> - Pkt. - -%% pick_dictionary/2 - -%% Pick the first dictionary that declares the application id in the -%% specified header. -pick_dictionary(Ds, [#diameter_header{application_id = Id} | _]) -> - pd(Ds, fun(D) -> Id = D:id() end); - -%% Pick the first dictionary that knows the specified message name. -pick_dictionary(Ds, [MsgName|_]) -> - pd(Ds, fun(D) -> D:msg2rec(MsgName) end); - -%% Pick the first dictionary that knows the name of the specified -%% message record. -pick_dictionary(Ds, Rec) -> - Name = element(1, Rec), - pd(Ds, fun(D) -> D:rec2msg(Name) end). - -pd([D|Ds], F) -> - try - F(D), - D - catch - error:_ -> - pd(Ds, F) - end; - -pd([], _) -> - ?ERROR(no_dictionary). - -%% send_request/4 - -send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, Timeout) - when node() == node(TPid) -> - %% Store the outgoing request before sending to avoid a race with - %% reply reception. - TRef = store_request(TPid, Bin, Req, Timeout), - send(TPid, Pkt), - TRef; - -%% Send using a remote transport: spawn a process on the remote node -%% to relay the answer. -send_request(TPid, #diameter_packet{} = Pkt, Req, Timeout) -> - TRef = erlang:start_timer(Timeout, self(), timeout), - T = {TPid, Pkt, Req, Timeout, TRef}, - spawn(node(TPid), ?MODULE, send, [T]), - TRef. - -%% send/1 - -send({TPid, Pkt, #request{handler = Pid} = Req, Timeout, TRef}) -> - Ref = send_request(TPid, Pkt, Req#request{handler = self()}, Timeout), - Pid ! reref(receive T -> T end, Ref, TRef). - -reref({T, Ref, R}, Ref, TRef) -> - {T, TRef, R}; -reref(T, _, _) -> - T. - -%% send/2 - -send(Pid, Pkt) -> - Pid ! {send, Pkt}. - -%% retransmit/4 - -retransmit({TPid, Caps, #diameter_app{alias = Alias} = App} = T, - #request{app = Alias, packet = Pkt0} - = Req, - SvcName, - Timeout) -> - have_request(Pkt0, TPid) %% Don't failover to a peer we've - andalso ?THROW(timeout), %% already sent to. - - #diameter_packet{header = Hdr0} = Pkt0, - Hdr = Hdr0#diameter_header{is_retransmitted = true}, - Pkt = Pkt0#diameter_packet{header = Hdr}, - - resend_req(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]), - T, - Req#request{packet = Pkt}, - Timeout, - []). - -resend_req({send, P}, T, #request{packet = Pkt} = Req, Timeout, Fs) -> - retransmit(make_request_packet(P, Pkt), T, Req, Timeout, Fs); - -resend_req({discard, Reason}, _, _, _, _) -> - ?THROW(Reason); - -resend_req(discard, _, _, _, _) -> - ?THROW(discarded); - -resend_req({eval_packet, RC, F}, T, Req, Timeout, Fs) -> - resend_req(RC, T, Req, Timeout, [F|Fs]); - -resend_req(T, {_, _, App}, _, _, _) -> - ?ERROR({invalid_return, prepare_retransmit, App, T}). - -%% retransmit/6 - -retransmit(Pkt, {TPid, Caps, _}, #request{dictionary = D} = Req0, Tmo, Fs) -> - EPkt = encode(D, Pkt, Fs), - - Req = Req0#request{transport = TPid, - packet = Pkt, - caps = Caps}, - - ?LOG(retransmission, Req), - TRef = send_request(TPid, EPkt, Req, Tmo), - {TRef, Req}. - -%% store_request/4 - -store_request(TPid, Bin, Req, Timeout) -> - Seqs = diameter_codec:sequence_numbers(Bin), - TRef = erlang:start_timer(Timeout, self(), timeout), - ets:insert(?REQUEST_TABLE, {Seqs, Req, TRef}), - ets:member(?REQUEST_TABLE, TPid) - orelse (self() ! {failover, TRef}), %% possibly missed failover - TRef. - -%% lookup_request/2 - -lookup_request(Msg, TPid) - when is_pid(TPid) -> - lookup(Msg, TPid, '_'); - -lookup_request(Msg, TRef) - when is_reference(TRef) -> - lookup(Msg, '_', TRef). - -lookup(Msg, TPid, TRef) -> - Seqs = diameter_codec:sequence_numbers(Msg), - Spec = [{{Seqs, #request{transport = TPid, _ = '_'}, TRef}, - [], - ['$_']}], - case ets:select(?REQUEST_TABLE, Spec) of - [{_, Req, _}] -> - Req; - [] -> - false - end. - -%% erase_request/1 - -erase_request(Pkt) -> - ets:delete(?REQUEST_TABLE, diameter_codec:sequence_numbers(Pkt)). - -%% match_requests/1 - -match_requests(TPid) -> - Pat = {'_', #request{transport = TPid, _ = '_'}, '_'}, - ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}]). - -%% have_request/2 - -have_request(Pkt, TPid) -> - Seqs = diameter_codec:sequence_numbers(Pkt), - Pat = {Seqs, #request{transport = TPid, _ = '_'}, '_'}, - '$end_of_table' /= ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}], 1). - -%% request_peer_up/1 - -request_peer_up(TPid) -> - ets:insert(?REQUEST_TABLE, {TPid}). - -%% request_peer_down/2 - -request_peer_down(TPid, S) -> - ets:delete(?REQUEST_TABLE, TPid), - lists:foreach(fun(T) -> failover(T,S) end, match_requests(TPid)). -%% Note that a request process can store its request after failover -%% notifications are sent here: store_request/4 sends the notification -%% in that case. Note also that we'll send as many notifications to a -%% given handler as there are peers its sent to. All but one of these -%% will be ignored. - -%%% --------------------------------------------------------------------------- -%%% recv_request/3 -%%% --------------------------------------------------------------------------- - -recv_request(TPid, Pkt, {ConnT, SvcName, Apps, Mask}) -> - try ets:lookup(ConnT, TPid) of - [C] -> - recv_request(C, TPid, Pkt, SvcName, Apps, Mask); - [] -> %% transport has gone down - ok - catch - error: badarg -> %% service has gone down (and taken table with it) - ok - end. - -%% recv_request/5 - -recv_request(#conn{apps = SApps, caps = Caps}, - TPid, - Pkt, - SvcName, - Apps, - Mask) -> - #diameter_caps{origin_host = {OH,_}, - origin_realm = {OR,_}} - = Caps, - - #diameter_packet{header = #diameter_header{application_id = Id}} - = Pkt, - - recv_request(find_recv_app(Id, SApps), - {SvcName, OH, OR}, - TPid, - Apps, - Mask, - Caps, - Pkt). - -%% find_recv_app/2 - -%% No one should be sending the relay identifier. -find_recv_app(?APP_ID_RELAY, _) -> - false; - -%% With any other id we either support it locally or as a relay. -find_recv_app(Id, SApps) -> - keyfind([Id, ?APP_ID_RELAY], 1, SApps). - -%% keyfind/3 - -keyfind([], _, _) -> - false; -keyfind([Key | Rest], Pos, L) -> - case lists:keyfind(Key, Pos, L) of - false -> - keyfind(Rest, Pos, L); - T -> - T - end. - -%% recv_request/7 - -recv_request({Id, Alias}, T, TPid, Apps, Mask, Caps, Pkt) -> - #diameter_app{dictionary = Dict} - = A - = find_app(Alias, Apps), - recv_request(T, - {TPid, Caps}, - A, - Mask, - diameter_codec:decode(Id, Dict, Pkt)); -%% Note that the decode is different depending on whether or not Id is -%% ?APP_ID_RELAY. - -%% DIAMETER_APPLICATION_UNSUPPORTED 3007 -%% A request was sent for an application that is not supported. - -recv_request(false, T, TPid, _, _, _, Pkt) -> - As = collect_avps(Pkt), - protocol_error(3007, T, TPid, Pkt#diameter_packet{avps = As}). - -collect_avps(Pkt) -> - case diameter_codec:collect_avps(Pkt) of - {_Bs, As} -> - As; - As -> - As - end. - -%% recv_request/5 - -%% Wrong number of bits somewhere in the message: reply. -%% -%% DIAMETER_INVALID_AVP_BITS 3009 -%% A request was received that included an AVP whose flag bits are -%% set to an unrecognized value, or that is inconsistent with the -%% AVP's definition. -%% -recv_request(T, {TPid, _}, _, _, #diameter_packet{errors = [Bs | _]} = Pkt) - when is_bitstring(Bs) -> - protocol_error(3009, T, TPid, Pkt); - -%% Either we support this application but don't recognize the command -%% or we're a relay and the command isn't proxiable. -%% -%% DIAMETER_COMMAND_UNSUPPORTED 3001 -%% The Request contained a Command-Code that the receiver did not -%% recognize or support. This MUST be used when a Diameter node -%% receives an experimental command that it does not understand. -%% -recv_request(T, - {TPid, _}, - #diameter_app{id = Id}, - _, - #diameter_packet{header = #diameter_header{is_proxiable = P}, - msg = M} - = Pkt) - when ?APP_ID_RELAY /= Id, undefined == M; - ?APP_ID_RELAY == Id, not P -> - protocol_error(3001, T, TPid, Pkt); - -%% Error bit was set on a request. -%% -%% DIAMETER_INVALID_HDR_BITS 3008 -%% A request was received whose bits in the Diameter header were -%% either set to an invalid combination, or to a value that is -%% inconsistent with the command code's definition. -%% -recv_request(T, - {TPid, _}, - _, - _, - #diameter_packet{header = #diameter_header{is_error = true}} - = Pkt) -> - protocol_error(3008, T, TPid, Pkt); - -%% A message in a locally supported application or a proxiable message -%% in the relay application. Don't distinguish between the two since -%% each application has its own callback config. That is, the user can -%% easily distinguish between the two cases. -recv_request(T, TC, App, Mask, Pkt) -> - request_cb(T, TC, App, Mask, examine(Pkt)). - -%% Note that there may still be errors but these aren't protocol -%% (3xxx) errors that lead to an answer-message. - -request_cb({SvcName, _OH, _OR} = T, TC, App, Mask, Pkt) -> - request_cb(cb(App, handle_request, [Pkt, SvcName, TC]), - App, - Mask, - T, - TC, - [], - Pkt). - -%% examine/1 -%% -%% Look for errors in a decoded message. Length errors result in -%% decode failure in diameter_codec. - -examine(#diameter_packet{header = #diameter_header{version - = ?DIAMETER_VERSION}} - = Pkt) -> - Pkt; - -%% DIAMETER_UNSUPPORTED_VERSION 5011 -%% This error is returned when a request was received, whose version -%% number is unsupported. - -examine(#diameter_packet{errors = Es} = Pkt) -> - Pkt#diameter_packet{errors = [5011 | Es]}. -%% It's odd/unfortunate that this isn't a protocol error. - -%% request_cb/7 - -%% A reply may be an answer-message, constructed either here or by -%% the handle_request callback. The header from the incoming request -%% is passed into the encode so that it can retrieve the relevant -%% command code in this case. It will also then ignore Dict and use -%% the base encoder. -request_cb({reply, Ans}, - #diameter_app{dictionary = Dict}, - _, - _, - {TPid, _}, - Fs, - Pkt) -> - reply(Ans, Dict, TPid, Fs, Pkt); - -%% An 3xxx result code, for which the E-bit is set in the header. -request_cb({protocol_error, RC}, _, _, T, {TPid, _}, Fs, Pkt) - when 3000 =< RC, RC < 4000 -> - protocol_error(RC, T, TPid, Fs, Pkt); - -%% RFC 3588 says we must reply 3001 to anything unrecognized or -%% unsupported. 'noreply' is undocumented (and inappropriately named) -%% backwards compatibility for this, protocol_error the documented -%% alternative. -request_cb(noreply, _, _, T, {TPid, _}, Fs, Pkt) -> - protocol_error(3001, T, TPid, Fs, Pkt); - -%% Relay a request to another peer. This is equivalent to doing an -%% explicit call/4 with the message in question except that (1) a loop -%% will be detected by examining Route-Record AVP's, (3) a -%% Route-Record AVP will be added to the outgoing request and (3) the -%% End-to-End Identifier will default to that in the -%% #diameter_header{} without the need for an end_to_end_identifier -%% option. -%% -%% relay and proxy are similar in that they require the same handling -%% with respect to Route-Record and End-to-End identifier. The -%% difference is that a proxy advertises specific applications, while -%% a relay advertises the relay application. If a callback doesn't -%% want to distinguish between the cases in the callback return value -%% then 'resend' is a neutral alternative. -%% -request_cb({A, Opts}, - #diameter_app{id = Id} - = App, - Mask, - T, - TC, - Fs, - Pkt) - when A == relay, Id == ?APP_ID_RELAY; - A == proxy, Id /= ?APP_ID_RELAY; - A == resend -> - resend(Opts, App, Mask, T, TC, Fs, Pkt); - -request_cb(discard, _, _, _, _, _, _) -> - ok; - -request_cb({eval_packet, RC, F}, App, Mask, T, TC, Fs, Pkt) -> - request_cb(RC, App, Mask, T, TC, [F|Fs], Pkt); - -request_cb({eval, RC, F}, App, Mask, T, TC, Fs, Pkt) -> - request_cb(RC, App, Mask, T, TC, Fs, Pkt), - diameter_lib:eval(F). - -%% protocol_error/5 - -protocol_error(RC, {_, OH, OR}, TPid, Fs, Pkt) -> - #diameter_packet{avps = Avps, errors = Es} = Pkt, - ?LOG({error, RC}, Pkt), - reply(answer_message({OH, OR, RC}, Avps), - ?BASE, - TPid, - Fs, - Pkt#diameter_packet{errors = [RC | Es]}). -%% Note that reply/5 may set the result code once more. It's set in -%% answer_message/2 in case reply/5 doesn't. - -%% protocol_error/4 - -protocol_error(RC, T, TPid, Pkt) -> - protocol_error(RC, T, TPid, [], Pkt). - -%% resend/7 -%% -%% Resend a message as a relay or proxy agent. - -resend(Opts, - #diameter_app{} = App, - Mask, - {_SvcName, OH, _OR} = T, - {_TPid, _Caps} = TC, - Fs, - #diameter_packet{avps = Avps} = Pkt) -> - {Code, _Flags, Vid} = ?BASE:avp_header('Route-Record'), - resend(is_loop(Code, Vid, OH, Avps), Opts, App, Mask, T, TC, Fs, Pkt). - -%% DIAMETER_LOOP_DETECTED 3005 -%% An agent detected a loop while trying to get the message to the -%% intended recipient. The message MAY be sent to an alternate peer, -%% if one is available, but the peer reporting the error has -%% identified a configuration problem. - -resend(true, _, _, _, T, {TPid, _}, Fs, Pkt) -> %% Route-Record loop - protocol_error(3005, T, TPid, Fs, Pkt); - -%% 6.1.8. Relaying and Proxying Requests -%% -%% A relay or proxy agent MUST append a Route-Record AVP to all requests -%% forwarded. The AVP contains the identity of the peer the request was -%% received from. - -resend(false, - Opts, - App, - Mask, - {SvcName, _, _} = T, - {TPid, #diameter_caps{origin_host = {_, OH}}}, - Fs, - #diameter_packet{header = Hdr0, - avps = Avps} - = Pkt) -> - Route = #diameter_avp{data = {?BASE, 'Route-Record', OH}}, - Seq = diameter_session:sequence(Mask), - Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq}, - Msg = [Hdr, Route | Avps], - resend(call(SvcName, App, Msg, Opts), T, TPid, Fs, Pkt). -%% The incoming request is relayed with the addition of a -%% Route-Record. Note the requirement on the return from call/4 below, -%% which places a requirement on the value returned by the -%% handle_answer callback of the application module in question. -%% -%% Note that there's nothing stopping the request from being relayed -%% back to the sender. A pick_peer callback may want to avoid this but -%% a smart peer might recognize the potential loop and choose another -%% route. A less smart one will probably just relay the request back -%% again and force us to detect the loop. A pick_peer that wants to -%% avoid this can specify filter to avoid the possibility. -%% Eg. {neg, {host, OH} where #diameter_caps{origin_host = {OH, _}}. -%% -%% RFC 6.3 says that a relay agent does not modify Origin-Host but -%% says nothing about a proxy. Assume it should behave the same way. - -%% resend/4 -%% -%% Relay a reply to a relayed request. - -%% Answer from the peer: reset the hop by hop identifier and send. -resend(#diameter_packet{bin = B} - = Pkt, - _, - TPid, - Fs, - #diameter_packet{header = #diameter_header{hop_by_hop_id = Id}, - transport_data = TD}) -> - P = Pkt#diameter_packet{bin = diameter_codec:hop_by_hop_id(Id, B), - transport_data = TD}, - eval_packet(P, Fs), - send(TPid, P); -%% TODO: counters - -%% Or not: DIAMETER_UNABLE_TO_DELIVER. -resend(_, T, TPid, Fs, Pkt) -> - protocol_error(3002, T, TPid, Fs, Pkt). - -%% is_loop/4 -%% -%% Is there a Route-Record AVP with our Origin-Host? - -is_loop(Code, - Vid, - Bin, - [#diameter_avp{code = Code, vendor_id = Vid, data = Bin} | _]) -> - true; - -is_loop(_, _, _, []) -> - false; - -is_loop(Code, Vid, OH, [_ | Avps]) - when is_binary(OH) -> - is_loop(Code, Vid, OH, Avps); - -is_loop(Code, Vid, OH, Avps) -> - is_loop(Code, Vid, ?BASE:avp(encode, OH, 'Route-Record'), Avps). - -%% reply/5 -%% -%% Send a locally originating reply. - -%% Skip the setting of Result-Code and Failed-AVP's below. This is -%% currently undocumented. -reply([Msg], Dict, TPid, Fs, Pkt) - when is_list(Msg); - is_tuple(Msg) -> - reply(Msg, Dict, TPid, Fs, Pkt#diameter_packet{errors = []}); - -%% No errors or a diameter_header/avp list. -reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = Es} = ReqPkt) - when [] == Es; - is_record(hd(Msg), diameter_header) -> - Pkt = diameter_codec:encode(Dict, make_answer_packet(Msg, ReqPkt)), - eval_packet(Pkt, Fs), - incr(send, Pkt, Dict, TPid), %% count result codes in sent answers - send(TPid, Pkt); - -%% Or not: set Result-Code and Failed-AVP AVP's. -reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = [H|_] = Es} = Pkt) -> - reply(rc(Msg, rc(H), [A || {_,A} <- Es], Dict), - Dict, - TPid, - Fs, - Pkt#diameter_packet{errors = []}). - -eval_packet(Pkt, Fs) -> - lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs). - -%% make_answer_packet/2 - -%% A reply message clears the R and T flags and retains the P flag. -%% The E flag will be set at encode. 6.2 of 3588 requires the same P -%% flag on an answer as on the request. A #diameter_packet{} returned -%% from a handle_request callback can circumvent this by setting its -%% own header values. -make_answer_packet(#diameter_packet{header = Hdr, - msg = Msg, - transport_data = TD}, - #diameter_packet{header = ReqHdr}) -> - Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, - is_request = false, - is_error = undefined, - is_retransmitted = false}, - #diameter_packet{header = fold_record(Hdr0, Hdr), - msg = Msg, - transport_data = TD}; - -%% Binaries and header/avp lists are sent as-is. -make_answer_packet(Bin, #diameter_packet{transport_data = TD}) - when is_binary(Bin) -> - #diameter_packet{bin = Bin, - transport_data = TD}; -make_answer_packet([#diameter_header{} | _] = Msg, - #diameter_packet{transport_data = TD}) -> - #diameter_packet{msg = Msg, - transport_data = TD}; - -%% Otherwise, preserve transport_data. -make_answer_packet(Msg, #diameter_packet{transport_data = TD} = Pkt) -> - make_answer_packet(#diameter_packet{msg = Msg, transport_data = TD}, Pkt). - -%% rc/1 - -rc({RC, _}) -> - RC; -rc(RC) -> - RC. - -%% rc/4 - -rc(#diameter_packet{msg = Rec} = Pkt, RC, Failed, Dict) -> - Pkt#diameter_packet{msg = rc(Rec, RC, Failed, Dict)}; - -rc(Rec, RC, Failed, Dict) - when is_integer(RC) -> - set(Rec, - lists:append([rc(Rec, {'Result-Code', RC}, Dict), - failed_avp(Rec, Failed, Dict)]), - Dict). - -%% Reply as name and tuple list ... -set([_|_] = Ans, Avps, _) -> - Ans ++ Avps; %% Values nearer tail take precedence. - -%% ... or record. -set(Rec, Avps, Dict) -> - Dict:'#set-'(Avps, Rec). - -%% rc/3 -%% -%% Turn the result code into a list if its optional and only set it if -%% 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]; - {0,1} -> [{K, [RC]}]; - _ -> [] - end; - -rc(Rec, T, Dict) -> - rc([Dict:rec2msg(element(1, Rec))], T, Dict). - -%% failed_avp/3 - -failed_avp(_, [] = No, _) -> - No; - -failed_avp(Rec, Failed, Dict) -> - [fa(Rec, [{'AVP', Failed}], Dict)]. - -%% Reply as name and tuple list ... -fa([MsgName | Values], FailedAvp, Dict) -> - R = Dict:msg2rec(MsgName), - try - Dict:'#info-'(R, {index, 'Failed-AVP'}), - {'Failed-AVP', [FailedAvp]} - catch - error: _ -> - Avps = proplists:get_value('AVP', Values, []), - A = #diameter_avp{name = 'Failed-AVP', - value = FailedAvp}, - {'AVP', [A|Avps]} - end; - -%% ... or record. -fa(Rec, FailedAvp, Dict) -> - try - {'Failed-AVP', [FailedAvp]} - catch - error: _ -> - Avps = Dict:'get-'('AVP', Rec), - A = #diameter_avp{name = 'Failed-AVP', - value = FailedAvp}, - {'AVP', [A|Avps]} - end. - -%% 3. Diameter Header -%% -%% E(rror) - If set, the message contains a protocol error, -%% and the message will not conform to the ABNF -%% described for this command. Messages with the 'E' -%% bit set are commonly referred to as error -%% messages. This bit MUST NOT be set in request -%% messages. See Section 7.2. - -%% 3.2. Command Code ABNF specification -%% -%% e-bit = ", ERR" -%% ; If present, the 'E' bit in the Command -%% ; Flags is set, indicating that the answer -%% ; message contains a Result-Code AVP in -%% ; the "protocol error" class. - -%% 7.1.3. Protocol Errors -%% -%% Errors that fall within the Protocol Error category SHOULD be treated -%% on a per-hop basis, and Diameter proxies MAY attempt to correct the -%% error, if it is possible. Note that these and only these errors MUST -%% only be used in answer messages whose 'E' bit is set. - -%% Thus, only construct answers to protocol errors. Other errors -%% require an message-specific answer and must be handled by the -%% application. - -%% 6.2. Diameter Answer Processing -%% -%% When a request is locally processed, the following procedures MUST be -%% applied to create the associated answer, in addition to any -%% additional procedures that MAY be discussed in the Diameter -%% application defining the command: -%% -%% - The same Hop-by-Hop identifier in the request is used in the -%% answer. -%% -%% - The local host's identity is encoded in the Origin-Host AVP. -%% -%% - The Destination-Host and Destination-Realm AVPs MUST NOT be -%% present in the answer message. -%% -%% - The Result-Code AVP is added with its value indicating success or -%% failure. -%% -%% - If the Session-Id is present in the request, it MUST be included -%% in the answer. -%% -%% - Any Proxy-Info AVPs in the request MUST be added to the answer -%% message, in the same order they were present in the request. -%% -%% - The 'P' bit is set to the same value as the one in the request. -%% -%% - The same End-to-End identifier in the request is used in the -%% answer. -%% -%% Note that the error messages (see Section 7.3) are also subjected to -%% the above processing rules. - -%% 7.3. Error-Message AVP -%% -%% The Error-Message AVP (AVP Code 281) is of type UTF8String. It MAY -%% accompany a Result-Code AVP as a human readable error message. The -%% Error-Message AVP is not intended to be useful in real-time, and -%% SHOULD NOT be expected to be parsed by network entities. - -%% answer_message/2 - -answer_message({OH, OR, RC}, Avps) -> - {Code, _, Vid} = ?BASE:avp_header('Session-Id'), - ['answer-message', {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Result-Code', RC} - | session_id(Code, Vid, Avps)]. - -session_id(Code, Vid, Avps) - when is_list(Avps) -> - try - {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps), - [{'Session-Id', [?BASE:avp(decode, D, 'Session-Id')]}] - catch - error: _ -> - [] - end. - -%% find_avp/3 - -find_avp(Code, Vid, Avps) - when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) -> - find(fun(A) -> is_avp(Code, Vid, A) end, Avps). - -%% The final argument here could be a list of AVP's, depending on the case, -%% but we're only searching at the top level. -is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) -> - true; -is_avp(_, _, _) -> - false. - -find(_, []) -> - false; -find(Pred, [H|T]) -> - case Pred(H) of - true -> - {value, H}; - false -> - find(Pred, T) - end. - -%% 7. Error Handling -%% -%% There are certain Result-Code AVP application errors that require -%% additional AVPs to be present in the answer. In these cases, the -%% Diameter node that sets the Result-Code AVP to indicate the error -%% MUST add the AVPs. Examples are: -%% -%% - An unrecognized AVP is received with the 'M' bit (Mandatory bit) -%% set, causes an answer to be sent with the Result-Code AVP set to -%% DIAMETER_AVP_UNSUPPORTED, and the Failed-AVP AVP containing the -%% offending AVP. -%% -%% - An AVP that is received with an unrecognized value causes an -%% answer to be returned with the Result-Code AVP set to -%% DIAMETER_INVALID_AVP_VALUE, with the Failed-AVP AVP containing the -%% AVP causing the error. -%% -%% - A command is received with an AVP that is omitted, yet is -%% mandatory according to the command's ABNF. The receiver issues an -%% answer with the Result-Code set to DIAMETER_MISSING_AVP, and -%% creates an AVP with the AVP Code and other fields set as expected -%% in the missing AVP. The created AVP is then added to the Failed- -%% AVP AVP. -%% -%% The Result-Code AVP describes the error that the Diameter node -%% encountered in its processing. In case there are multiple errors, -%% the Diameter node MUST report only the first error it encountered -%% (detected possibly in some implementation dependent order). The -%% specific errors that can be described by this AVP are described in -%% the following section. - -%% 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. -%% -%% The possible reasons for this AVP are the presence of an improperly -%% constructed AVP, an unsupported or unrecognized AVP, an invalid AVP -%% value, the omission of a required AVP, the presence of an explicitly -%% excluded AVP (see tables in Section 10), or the presence of two or -%% more occurrences of an AVP which is restricted to 0, 1, or 0-1 -%% occurrences. -%% -%% A Diameter message MAY contain one Failed-AVP AVP, containing the -%% entire AVP that could not be processed successfully. If the failure -%% reason is omission of a required AVP, an AVP with the missing AVP -%% code, the missing vendor id, and a zero filled payload of the minimum -%% required length for the omitted AVP will be added. - -%%% --------------------------------------------------------------------------- -%%% # handle_answer/3 -%%% --------------------------------------------------------------------------- - -%% Process an answer message in call-specific process. - -handle_answer(SvcName, _, {error, Req, Reason}) -> - handle_error(Req, Reason, SvcName); - -handle_answer(SvcName, - AnswerErrors, - {answer, #request{dictionary = Dict} = Req, Pkt}) -> - answer(examine(diameter_codec:decode(Dict, Pkt)), - SvcName, - AnswerErrors, - Req). - -%% We don't really need to do a full decode if we're a relay and will -%% just resend with a new hop by hop identifier, but might a proxy -%% want to examine the answer? - -answer(Pkt, SvcName, AE, #request{transport = TPid, - dictionary = Dict} - = Req) -> - try - incr(recv, Pkt, Dict, TPid) - of - _ -> a(Pkt, SvcName, AE, Req) - catch - exit: {invalid_error_bit, _} = E -> - a(Pkt#diameter_packet{errors = [E]}, SvcName, AE, Req) - end. - -a(#diameter_packet{errors = Es} = Pkt, SvcName, AE, #request{transport = TPid, - caps = Caps, - packet = P} - = Req) - when [] == Es; - callback == AE -> - cb(Req, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); - -a(Pkt, SvcName, report, Req) -> - x(errors, handle_answer, [SvcName, Req, Pkt]); - -a(Pkt, SvcName, discard, Req) -> - x({errors, handle_answer, [SvcName, Req, Pkt]}). - -%% Note that we don't check that the application id in the answer's -%% header is what we expect. (TODO: Does the rfc says anything about -%% this?) - -%% incr/4 -%% -%% Increment a stats counter for an incoming or outgoing message. - -%% TODO: fix -incr(_, #diameter_packet{msg = undefined}, _, _) -> - ok; - -incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid) -> - incr(TPid, {diameter_codec:msg_id(H), D, error}); - -incr(Dir, Pkt, Dict, TPid) -> - #diameter_packet{header = #diameter_header{is_error = E} - = Hdr, - msg = Rec} - = Pkt, - - RC = int(get_avp_value(Dict, 'Result-Code', Rec)), - PE = is_protocol_error(RC), - - %% Check that the E bit is set only for 3xxx result codes. - (not (E orelse PE)) - orelse (E andalso PE) - orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]), - - irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)). - -irc(_, _, _, undefined) -> - false; - -irc(TPid, Hdr, Dir, Ctr) -> - incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}). - -%% incr/2 - -incr(TPid, Counter) -> - diameter_stats:incr(Counter, TPid, 1). - -%% error_counter/2 - -%% RFC 3588, 7.6: -%% -%% All Diameter answer messages defined in vendor-specific -%% applications MUST include either one Result-Code AVP or one -%% Experimental-Result AVP. -%% -%% Maintain statistics assuming one or the other, not both, which is -%% surely the intent of the RFC. - -rc_counter(Dict, Rec, undefined) -> - er(get_avp_value(Dict, 'Experimental-Result', Rec)); -rc_counter(_, _, RC) -> - {'Result-Code', RC}. - -%% Outgoing answers may be in any of the forms messages can be sent -%% in. Incoming messages will be records. We're assuming here that the -%% arity of the result code AVP's is 0 or 1. - -er([{_,_,N} = T | _]) - when is_integer(N) -> - T; -er({_,_,N} = T) - when is_integer(N) -> - T; -er(_) -> - undefined. - -%% Extract the first good looking integer. There's no guarantee -%% that what we're looking for has arity 1. -int([N|_]) - when is_integer(N) -> - N; -int(N) - when is_integer(N) -> - N; -int(_) -> - undefined. - -is_protocol_error(RC) -> - 3000 =< RC andalso RC < 4000. - --spec x(any(), atom(), list()) -> no_return(). - -%% Warn and exit request process on errors in an incoming answer. -x(Reason, F, A) -> - diameter_lib:warning_report(Reason, {?MODULE, F, A}), - x(Reason). - -x(T) -> - exit(T). - -%%% --------------------------------------------------------------------------- -%%% # failover/[23] -%%% --------------------------------------------------------------------------- - -%% Failover as a consequence of request_peer_down/2. -failover({_, #request{handler = Pid} = Req, TRef}, S) -> - Pid ! {failover, TRef, rt(Req, S)}. - -%% Failover as a consequence of store_request/4. -failover(TRef, Seqs, S) - when is_reference(TRef) -> - case lookup_request(Seqs, TRef) of - #request{} = Req -> - failover({Seqs, Req, TRef}, S); - false -> - ok - end. - -%% prepare_request returned a binary ... -rt(#request{packet = #diameter_packet{msg = undefined}}, _) -> - false; %% TODO: Not what we should do. - -%% ... or not. -rt(#request{packet = #diameter_packet{msg = Msg}, - dictionary = Dict} - = Req, - S) -> - find_transport(get_destination(Dict, Msg), Req, S). - -%%% --------------------------------------------------------------------------- -%%% # report_status/5 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # report_status/5 +%% --------------------------------------------------------------------------- report_status(Status, - #peer{ref = Ref, - conn = TPid, - type = Type, - options = Opts}, - #conn{apps = [_|_] = As, + #watchdog{ref = Ref, + peer = TPid, + type = Type, + options = Opts}, + #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)). @@ -2615,222 +1280,77 @@ send_event(SvcName, Info) -> send_event(#diameter_event{service = SvcName} = E) -> lists:foreach(fun({_, Pid}) -> Pid ! E end, subscriptions(SvcName)). -%%% --------------------------------------------------------------------------- -%%% # share_peer/5 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # 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. -%%% --------------------------------------------------------------------------- -%%% # 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/2 +%% --------------------------------------------------------------------------- -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). -%%% --------------------------------------------------------------------------- -%%% # remote_peer_up/4 -%%% --------------------------------------------------------------------------- +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). -%%% --------------------------------------------------------------------------- -%%% # remote_peer_down/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # 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) -> ?Dict:update(Alias, fun(Ps) -> lists:keydelete(Pid, 1, Ps) end, PDict). -%%% --------------------------------------------------------------------------- -%%% find_transport/[34] -%%% -%%% Output: {TransportPid, #diameter_caps{}, #diameter_app{}} -%%% | false -%%% | {error, Reason} -%%% --------------------------------------------------------------------------- - -%% Initial call, from an arbitrary process. -find_transport({alias, Alias}, Msg, Opts, #state{service = Svc} = S) -> - #diameter_service{applications = Apps} = Svc, - ft(find_send_app(Alias, Apps), Msg, Opts, S); - -%% Relay or proxy send. -find_transport(#diameter_app{} = App, Msg, Opts, S) -> - ft(App, Msg, Opts, S). - -ft(#diameter_app{module = Mod, dictionary = Dict} = App, Msg, Opts, S) -> - #options{filter = Filter, - extra = Xtra} - = Opts, - pick_peer(App#diameter_app{module = Mod ++ Xtra}, - get_destination(Dict, Msg), - Filter, - S); -ft(false = No, _, _, _) -> - No. - -%% This can't be used if we're a relay and sending a message -%% in an application not known locally. (TODO) -find_send_app(Alias, Apps) -> - case lists:keyfind(Alias, #diameter_app.alias, Apps) of - #diameter_app{id = ?APP_ID_RELAY} -> - false; - T -> - T - end. - -%% Retransmission, in the service process. -find_transport([_,_] = RH, - Req, - #state{service = #diameter_service{pid = Pid, - applications = Apps}} - = S) - when self() == Pid -> - #request{app = Alias, - filter = Filter, - module = ModX} - = Req, - #diameter_app{} - = App - = lists:keyfind(Alias, #diameter_app.alias, Apps), - - pick_peer(App#diameter_app{module = ModX}, - RH, - Filter, - S). - -%% get_destination/2 - -get_destination(Dict, Msg) -> - [str(get_avp_value(Dict, 'Destination-Realm', Msg)), - str(get_avp_value(Dict, 'Destination-Host', Msg))]. - -%% This is not entirely correct. The avp could have an arity 1, in -%% which case an empty list is a DiameterIdentity of length 0 rather -%% than the list of no values we treat it as by mapping to undefined. -%% This behaviour is documented. -str([]) -> - undefined; -str(T) -> - T. - -%% get_avp_value/3 -%% -%% Find an AVP in a message of one of three forms: -%% -%% - a message record (as generated from a .dia spec) or -%% - a list of an atom message name followed by 2-tuple, avp name/value pairs. -%% - a list of a #diameter_header{} followed by #diameter_avp{} records, -%% -%% In the first two forms a dictionary module is used at encode to -%% identify the type of the AVP and its arity in the message in -%% question. The third form allows messages to be sent as is, without -%% a dictionary, which is needed in the case of relay agents, for one. - -%% Messages will be header/avps list as a relay and the only AVP's we -%% look for are in the common dictionary. This is required since the -%% relay dictionary doesn't inherit the common dictionary (which maybe -%% it should). -get_avp_value(?RELAY, Name, Msg) -> - get_avp_value(?BASE, Name, Msg); - -%% Message sent as a header/avps list, probably a relay case but not -%% necessarily. -get_avp_value(Dict, Name, [#diameter_header{} | Avps]) -> - try - {Code, _, VId} = Dict:avp_header(Name), - [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) -> - C /= Code orelse V /= VId - end, - Avps), - avp_decode(Dict, Name, A) - catch - error: _ -> - undefined - end; - -%% Outgoing message as a name/values list. -get_avp_value(_, Name, [_MsgName | Avps]) -> - case lists:keyfind(Name, 1, Avps) of - {_, V} -> - V; - _ -> - undefined - end; - -%% Record might be an answer message in the common dictionary. -get_avp_value(Dict, Name, Rec) - when Dict /= ?BASE, element(1, Rec) == 'diameter_base_answer-message' -> - get_avp_value(?BASE, Name, Rec); - -%% Message is typically a record but not necessarily: diameter:call/4 -%% can be passed an arbitrary term. -get_avp_value(Dict, Name, Rec) -> - try - Dict:'#get-'(Name, Rec) - catch - error:_ -> - undefined - end. - -avp_decode(Dict, Name, #diameter_avp{value = undefined, - data = Bin}) -> - Dict:avp(decode, Bin, Name); -avp_decode(_, _, #diameter_avp{value = V}) -> - V. - -%%% --------------------------------------------------------------------------- -%%% # pick_peer(App, [DestRealm, DestHost], Filter, #state{}) -%%% -%%% Output: {TransportPid, #diameter_caps{}, App} -%%% | false -%%% | {error, Reason} -%%% --------------------------------------------------------------------------- - -%% Find transports to a given realm/host. +%% --------------------------------------------------------------------------- +%% pick_peer/4 +%% --------------------------------------------------------------------------- pick_peer(#diameter_app{alias = Alias} = App, - [_,_] = RH, + RealmAndHost, Filter, #state{local_peers = L, shared_peers = S, service_name = SvcName, service = #diameter_service{pid = Pid}}) -> - pick_peer(peers(Alias, RH, Filter, L), - peers(Alias, RH, Filter, S), + pick_peer(peers(Alias, RealmAndHost, Filter, L), + peers(Alias, RealmAndHost, Filter, S), Pid, SvcName, App). @@ -2843,7 +1363,12 @@ pick_peer([], [], _, _, _) -> %% App state is mutable but we're not in the service process: go there. pick_peer(Local, Remote, Pid, _SvcName, #diameter_app{mutable = true} = App) when self() /= Pid -> - call_service(Pid, {pick_peer, Local, Remote, App}); + case call_service(Pid, {pick_peer, Local, Remote, App}) of + {TPid, _} = T when is_pid(TPid) -> + T; + {error, _} -> + false + end; %% App state isn't mutable or it is and we're in the service process: %% do the deed. @@ -2851,19 +1376,18 @@ pick_peer(Local, Remote, _Pid, SvcName, - #diameter_app{module = ModX, - alias = Alias, + #diameter_app{alias = Alias, init_state = S, mutable = M} = App) -> - MFA = {ModX, pick_peer, [Local, Remote, SvcName]}, + Args = [Local, Remote, SvcName], - try state_cb(App, MFA) of - {ok, {TPid, #diameter_caps{} = Caps}} when is_pid(TPid) -> - {TPid, Caps, App}; - {{TPid, #diameter_caps{} = Caps}, ModS} when is_pid(TPid), M -> + try state_cb(App, pick_peer, Args) of + {ok, {TPid, #diameter_caps{}} = T} when is_pid(TPid) -> + T; + {{TPid, #diameter_caps{}} = T, ModS} when is_pid(TPid), M -> mod_state(Alias, ModS), - {TPid, Caps, App}; + T; {false = No, ModS} when M -> mod_state(Alias, ModS), No; @@ -2871,15 +1395,17 @@ pick_peer(Local, No; false = No -> No; - {{TPid, #diameter_caps{} = Caps}, S} when is_pid(TPid) -> - {TPid, Caps, App}; %% Accept returned state in the immutable + {{TPid, #diameter_caps{}} = T, S} when is_pid(TPid) -> + T; %% Accept returned state in the immutable {false = No, S} -> %% case as long it isn't changed. No; T -> - diameter_lib:error_report({invalid, T, App}, MFA) + diameter_lib:error_report({invalid, T, App}, + {App, pick_peer, Args}) catch E: Reason -> - diameter_lib:error_report({failure, {E, Reason, ?STACK}}, MFA) + diameter_lib:error_report({failure, {E, Reason, ?STACK}}, + {App, pick_peer, Args}) end. %% peers/4 @@ -2966,14 +1492,14 @@ eq(Any, Id, PeerId) -> %% transports/1 -transports(#state{peerT = PeerT}) -> - ets:select(PeerT, [{#peer{conn = '$1', _ = '_'}, +transports(#state{watchdogT = WatchdogT}) -> + ets:select(WatchdogT, [{#watchdog{peer = '$1', _ = '_'}, [{'is_pid', '$1'}], ['$1']}]). -%%% --------------------------------------------------------------------------- -%%% # service_info/2 -%%% --------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% # service_info/2 +%% --------------------------------------------------------------------------- %% The config passed to diameter:start_service/2. -define(CAP_INFO, ['Origin-Host', @@ -3021,11 +1547,12 @@ tagged_info(Item, S) undefined end; -tagged_info(TPid, #state{peerT = PT, connT = CT}) +tagged_info(TPid, #state{watchdogT = WatchdogT, peerT = PeerT}) when is_pid(TPid) -> try - [#conn{peer = Pid}] = ets:lookup(CT, TPid), - [#peer{ref = Ref, type = Type, options = Opts}] = ets:lookup(PT, Pid), + [#peer{watchdog = Pid}] = ets:lookup(PeerT, TPid), + [#watchdog{ref = Ref, type = Type, options = Opts}] + = ets:lookup(WatchdogT, Pid), [{ref, Ref}, {type, Type}, {options, Opts}] @@ -3108,11 +1635,11 @@ complete(Pre) -> %% info_stats/1 -info_stats(#state{peerT = PeerT}) -> - MatchSpec = [{#peer{ref = '$1', conn = '$2', _ = '_'}, +info_stats(#state{watchdogT = WatchdogT}) -> + MatchSpec = [{#watchdog{ref = '$1', peer = '$2', _ = '_'}, [{'is_pid', '$2'}], [['$1', '$2']]}], - try ets:select(PeerT, MatchSpec) of + try ets:select(WatchdogT, MatchSpec) of L -> diameter_stats:read(lists:append(L)) catch @@ -3122,20 +1649,15 @@ info_stats(#state{peerT = PeerT}) -> %% info_transport/1 %% %% One entry per configured transport. Statistics for each entry are -%% the accumulated values for the ref and associated peer pids. +%% the accumulated values for the ref and associated watchdog/peer +%% pids. 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). @@ -3155,43 +1677,42 @@ transport([[_,_] | L]) -> %% Possibly many peer entries for a listening transport. Note that all %% have the same options by construction, which is not terribly space -%% efficient. (TODO: all entries for the same Ref should share options.) +%% efficient. transport([[{type, accept}, {options, Opts} | _] | _] = Ls) -> [{type, listen}, {options, Opts}, {accept, [lists:nthtail(2,L) || L <- Ls]}]. -peer_dict(#state{peerT = PeerT, connT = ConnT}, Dict0) -> - try ets:tab2list(PeerT) of +peer_dict(#state{watchdogT = WatchdogT, peerT = PeerT}, Dict0) -> + try ets:tab2list(WatchdogT) of L -> - lists:foldl(fun(T,A) -> peer_acc(ConnT, A, T) end, Dict0, L) + lists:foldl(fun(T,A) -> peer_acc(PeerT, A, T) end, Dict0, L) catch error: badarg -> Dict0 %% service has gone down end. -peer_acc(ConnT, Acc, #peer{pid = Pid, - type = Type, - ref = Ref, - options = Opts, - op_state = OS, - started = T, - conn = TPid}) -> - WS = wd_state(OS), +peer_acc(PeerT, Acc, #watchdog{pid = Pid, + type = Type, + ref = Ref, + options = Opts, + state = WS, + started = At, + peer = TPid}) -> dict:append(Ref, [{type, Type}, {options, Opts}, - {watchdog, {Pid, T, WS}} - | info_conn(ConnT, TPid, WS /= ?WD_DOWN)], + {watchdog, {Pid, At, WS}} + | info_peer(PeerT, TPid, WS)], Acc). -info_conn(ConnT, TPid, true) - when is_pid(TPid) -> - try ets:lookup(ConnT, TPid) of - T -> info_conn(T) +info_peer(PeerT, TPid, WS) + when is_pid(TPid), WS /= ?WD_DOWN -> + try ets:lookup(PeerT, TPid) of + T -> info_peer(T) catch error: badarg -> [] %% service has gone down end; -info_conn(_, _, _) -> +info_peer(_, _, _) -> []. %% The point of extracting the config here is so that 'transport' info @@ -3210,19 +1731,12 @@ config_acc({Ref, T, Opts}, Dict) config_acc(_, Dict) -> Dict. -wd_state({_,S}) -> - S; -wd_state(?STATE_UP) -> - ?WD_OKAY; -wd_state(?STATE_DOWN) -> - ?WD_DOWN. - -info_conn([#conn{pid = Pid, apps = SApps, caps = Caps, started = T}]) -> +info_peer([#peer{pid = Pid, apps = SApps, caps = Caps, started = T}]) -> [{peer, {Pid, T}}, {apps, SApps}, {caps, info_caps(Caps)} | try [{port, info_port(Pid)}] catch _:_ -> [] end]; -info_conn([] = No) -> +info_peer([] = No) -> No. %% Extract information that the processes involved are expected to @@ -3256,22 +1770,7 @@ mk_app(#diameter_app{} = A) -> %% One entry for each outgoing request whose answer is outstanding. info_pending(#state{} = S) -> - MatchSpec = [{{'$1', - #request{transport = '$2', - from = '$3', - app = '$4', - _ = '_'}, - '_'}, - [?ORCOND([{'==', T, '$2'} || T <- transports(S)])], - [{{'$1', [{{app, '$4'}}, - {{transport, '$2'}}, - {{from, '$3'}}]}}]}], - - try - ets:select(?REQUEST_TABLE, MatchSpec) - catch - error: badarg -> [] %% service has gone down - end. + diameter_traffic:pending(transports(S)). %% info_connections/1 %% diff --git a/lib/diameter/src/base/diameter_stats.erl b/lib/diameter/src/base/diameter_stats.erl index 70727d068e..b68d4af11f 100644 --- a/lib/diameter/src/base/diameter_stats.erl +++ b/lib/diameter/src/base/diameter_stats.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 @@ -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. @@ -136,13 +190,12 @@ read(Refs, B) -> -spec flush([ref()]) -> [{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 new file mode 100644 index 0000000000..25b902e3f2 --- /dev/null +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -0,0 +1,1707 @@ +%% +%% %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% +%% + +%% +%% Implements the handling of incoming and outgoing Diameter messages +%% except CER/CEA, DWR/DWA and DPR/DPA. That is, the messages that a +%% diameter client sends and receives. +%% + +-module(diameter_traffic). + +%% towards diameter +-export([send_request/4]). + +%% towards diameter_watchdog +-export([receive_message/4]). + +%% towards diameter_service +-export([make_recvdata/1, + peer_up/1, + peer_down/1, + failover/1, + pending/1]). + +%% towards ?MODULE +-export([send/1]). %% send from remote node + +-include_lib("diameter/include/diameter.hrl"). +-include("diameter_internal.hrl"). + +-define(RELAY, ?DIAMETER_DICT_RELAY). +-define(BASE, ?DIAMETER_DICT_COMMON). %% Note: the RFC 3588 dictionary + +-define(DEFAULT_TIMEOUT, 5000). %% for outgoing requests + +%% Table containing outgoing requests for which a reply has yet to be +%% received. +-define(REQUEST_TABLE, diameter_request). + +%% Workaround for dialyzer's lack of understanding of match specs. +-type match(T) + :: T | '_' | '$1' | '$2' | '$3' | '$4'. + +%% Record diameter:call/4 options are parsed into. +-record(options, + {filter = none :: diameter:peer_filter(), + extra = [] :: list(), + timeout = ?DEFAULT_TIMEOUT :: 0..16#FFFFFFFF, + detach = false :: boolean()}). + +%% Term passed back to receive_message/4 with every incoming message. +-record(recvdata, + {peerT :: ets:tid(), + service_name :: diameter:service_name(), + apps :: [#diameter_app{}], + sequence :: diameter:sequence()}). + +%% Record stored in diameter_request for each outgoing request. +-record(request, + {ref :: match(reference()), %% used to receive answer + caller :: match(pid()), %% calling process + handler :: match(pid()), %% request process + transport :: match(pid()), %% peer process + caps :: match(#diameter_caps{}), %% of connection + packet :: match(#diameter_packet{})}). %% of request + +%% --------------------------------------------------------------------------- +%% # make_recvdata/1 +%% --------------------------------------------------------------------------- + +make_recvdata([SvcName, PeerT, Apps, Mask | _]) -> + #recvdata{service_name = SvcName, + peerT = PeerT, + apps = Apps, + sequence = Mask}. +%% Take a list so that the caller (diameter_service) can be upgraded +%% first if new members are added. Note that receive_message/4 might +%% still get an old term from any watchdog started in old code. + +%% --------------------------------------------------------------------------- +%% peer_up/1 +%% --------------------------------------------------------------------------- + +%% Insert an element that is used to detect whether or not there has +%% been a failover when inserting an outgoing request. +peer_up(TPid) -> + ets:insert(?REQUEST_TABLE, {TPid}). + +%% --------------------------------------------------------------------------- +%% peer_down/1 +%% --------------------------------------------------------------------------- + +peer_down(TPid) -> + ets:delete(?REQUEST_TABLE, TPid), + failover(TPid). + +%% --------------------------------------------------------------------------- +%% pending/1 +%% --------------------------------------------------------------------------- + +pending(TPids) -> + MatchSpec = [{{'$1', + #request{caller = '$2', + handler = '$3', + transport = '$4', + _ = '_'}, + '_'}, + [?ORCOND([{'==', T, '$4'} || T <- TPids])], + [{{'$1', [{{caller, '$2'}}, + {{handler, '$3'}}, + {{transport, '$4'}}]}}]}], + + try + ets:select(?REQUEST_TABLE, MatchSpec) + catch + error: badarg -> [] %% service has gone down + end. + +%% --------------------------------------------------------------------------- +%% # receive_message/4 +%% +%% Handle an incoming Diameter message. +%% --------------------------------------------------------------------------- + +%% Handle an incoming Diameter message in the watchdog process. This +%% used to come through the service process but this avoids that +%% becoming a bottleneck. + +receive_message(TPid, Pkt, Dict0, RecvData) + when is_pid(TPid) -> + #diameter_packet{header = #diameter_header{is_request = R}} = Pkt, + recv(R, + (not R) andalso lookup_request(Pkt, TPid), + TPid, + Pkt, + Dict0, + RecvData). + +%% Incoming request ... +recv(true, false, TPid, Pkt, Dict0, RecvData) -> + try + spawn(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end) + catch + error: system_limit = E -> %% discard + ?LOG({error, E}, now()) + end; + +%% ... answer to known request ... +recv(false, #request{ref = Ref, handler = Pid} = Req, _, Pkt, Dict0, _) -> + Pid ! {answer, Ref, Req, Dict0, Pkt}; +%% Note that failover could have happened prior to this message being +%% received and triggering failback. That is, both a failover message +%% and answer may be on their way to the handler process. In the worst +%% case the request process gets notification of the failover and +%% sends to the alternate peer before an answer arrives, so it's +%% always the case that we can receive more than one answer after +%% failover. The first answer received by the request process wins, +%% any others are discarded. + +%% ... or not. +recv(false, false, _, _, _, _) -> + ok. + +%% --------------------------------------------------------------------------- +%% recv_request/4 +%% --------------------------------------------------------------------------- + +recv_request(TPid, + #diameter_packet{header = #diameter_header{application_id = Id}} + = Pkt, + Dict0, + #recvdata{peerT = PeerT, apps = Apps} + = RecvData) -> + send_A(recv_R(diameter_service:find_incoming_app(PeerT, TPid, Id, Apps), + TPid, + Pkt, + Dict0, + RecvData), + TPid, + Dict0, + RecvData). + +%% recv_R/5 + +recv_R({#diameter_app{id = Id, dictionary = Dict} = App, Caps}, + TPid, + Pkt0, + Dict0, + RecvData) -> + Pkt = errors(Id, diameter_codec:decode(Id, Dict, Pkt0)), + {Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)}; +%% Note that the decode is different depending on whether or not Id is +%% ?APP_ID_RELAY. + +%% DIAMETER_APPLICATION_UNSUPPORTED 3007 +%% A request was sent for an application that is not supported. + +recv_R(#diameter_caps{} + = Caps, + _TPid, + #diameter_packet{errors = Es} + = Pkt, + _Dict0, + _RecvData) -> + {Caps, Pkt#diameter_packet{avps = collect_avps(Pkt), + errors = [3007 | Es]}}; + +recv_R(false = No, _, _, _, _) -> %% transport has gone down + No. + +collect_avps(Pkt) -> + case diameter_codec:collect_avps(Pkt) of + {_Bs, As} -> + As; + As -> + As + end. + +%% recv_R/6 + +%% Answer errors ourselves ... +recv_R(#diameter_app{options = [_, {request_errors, E} | _]}, + _TPid, + Dict0, + _Caps, + _RecvData, + #diameter_packet{errors = [RC|_]}) %% a detected 3xxx is hd + when E == answer, (Dict0 /= ?BASE orelse 3 == RC div 1000); + E == answer_3xxx, 3 == RC div 1000 -> + {{answer_message, rc(RC)}, [], []}; + +%% ... or make a handle_request callback. Note that +%% Pkt#diameter_packet.msg = undefined in the 3001 case. +recv_R(App, + TPid, + _Dict0, + Caps, + #recvdata{service_name = SvcName}, + Pkt) -> + request_cb(cb(App, handle_request, [Pkt, SvcName, {TPid, Caps}]), + App, + [], + []). + +rc({N,_}) -> + N; +rc(N) -> + N. + +%% errors/1 +%% +%% Look for additional errors in a decoded message, prepending the +%% errors field with the first detected error. It's odd/unfortunate +%% that 501[15] aren't protocol errors. With RFC 3588 this means that +%% a handle_request callback has to formulate the answer. With RFC +%% 6733 it's acceptable for 5xxx to be sent in an answer-message. + +%% DIAMETER_INVALID_MESSAGE_LENGTH 5015 +%% This error is returned when a request is received with an invalid +%% message length. + +errors(_, #diameter_packet{header = #diameter_header{length = Len}, + bin = Bin, + errors = Es} + = Pkt) + when Len < 20; + 0 /= Len rem 4; + 8*Len /= bit_size(Bin) -> + Pkt#diameter_packet{errors = [5015 | Es]}; + +%% DIAMETER_UNSUPPORTED_VERSION 5011 +%% This error is returned when a request was received, whose version +%% number is unsupported. + +errors(_, #diameter_packet{header = #diameter_header{version = V}, + errors = Es} + = Pkt) + when V /= ?DIAMETER_VERSION -> + Pkt#diameter_packet{errors = [5011 | Es]}; + +%% DIAMETER_INVALID_AVP_BITS 3009 +%% A request was received that included an AVP whose flag bits are +%% set to an unrecognized value, or that is inconsistent with the +%% AVP's definition. + +errors(_, #diameter_packet{errors = [Bs | Es]} = Pkt) + when is_bitstring(Bs) -> + Pkt#diameter_packet{errors = [3009 | Es]}; + +%% DIAMETER_COMMAND_UNSUPPORTED 3001 +%% The Request contained a Command-Code that the receiver did not +%% recognize or support. This MUST be used when a Diameter node +%% receives an experimental command that it does not understand. + +errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P}, + msg = M, + errors = Es} + = Pkt) + when ?APP_ID_RELAY /= Id, undefined == M; %% don't know the command + ?APP_ID_RELAY == Id, not P -> %% command isn't proxiable + Pkt#diameter_packet{errors = [3001 | Es]}; + +%% DIAMETER_INVALID_HDR_BITS 3008 +%% A request was received whose bits in the Diameter header were +%% either set to an invalid combination, or to a value that is +%% inconsistent with the command code's definition. + +errors(_, #diameter_packet{header = #diameter_header{is_request = true, + is_error = true}, + errors = Es} + = Pkt) -> + Pkt#diameter_packet{errors = [3008 | Es]}; + +%% Green. +errors(_, Pkt) -> + Pkt. + +%% request_cb/4 + +%% A reply may be an answer-message, constructed either here or by +%% the handle_request callback. The header from the incoming request +%% is passed into the encode so that it can retrieve the relevant +%% command code in this case. It will also then ignore Dict and use +%% the base encoder. +request_cb({reply, _Ans} = T, _App, EvalPktFs, EvalFs) -> + {T, EvalPktFs, EvalFs}; + +%% An 3xxx result code, for which the E-bit is set in the header. +request_cb({protocol_error, RC}, _App, EvalPktFs, EvalFs) + when 3 == RC div 1000 -> + {{answer_message, RC}, EvalPktFs, EvalFs}; + +request_cb({answer_message, RC} = T, _App, EvalPktFs, EvalFs) + when 3 == RC div 1000; + 5 == RC div 1000 -> + {T, EvalPktFs, EvalFs}; + +%% RFC 3588 says we must reply 3001 to anything unrecognized or +%% unsupported. 'noreply' is undocumented (and inappropriately named) +%% backwards compatibility for this, protocol_error the documented +%% alternative. +request_cb(noreply, _App, EvalPktFs, EvalFs) -> + {{answer_message, 3001}, EvalPktFs, EvalFs}; + +%% Relay a request to another peer. This is equivalent to doing an +%% explicit call/4 with the message in question except that (1) a loop +%% will be detected by examining Route-Record AVP's, (3) a +%% Route-Record AVP will be added to the outgoing request and (3) the +%% End-to-End Identifier will default to that in the +%% #diameter_header{} without the need for an end_to_end_identifier +%% option. +%% +%% relay and proxy are similar in that they require the same handling +%% with respect to Route-Record and End-to-End identifier. The +%% difference is that a proxy advertises specific applications, while +%% a relay advertises the relay application. If a callback doesn't +%% want to distinguish between the cases in the callback return value +%% then 'resend' is a neutral alternative. +%% +request_cb({A, Opts}, #diameter_app{id = Id}, EvalPktFs, EvalFs) + when A == relay, Id == ?APP_ID_RELAY; + A == proxy, Id /= ?APP_ID_RELAY; + A == resend -> + {{call, Opts}, EvalPktFs, EvalFs}; + +request_cb(discard = No, _, _, _) -> + No; + +request_cb({eval_packet, RC, F}, App, Fs, EvalFs) -> + request_cb(RC, App, [F|Fs], EvalFs); + +request_cb({eval, RC, F}, App, EvalPktFs, Fs) -> + request_cb(RC, App, EvalPktFs, [F|Fs]); + +request_cb(T, App, _, _) -> + ?ERROR({invalid_return, T, handle_request, App}). + +%% send_A/4 + +send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application + #diameter_packet{errors = [RC|_]} = Pkt, + send_A(answer_message(RC, Caps, Dict0, Pkt), + TPid, + Dict0, + Pkt, + [], + []); + +send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) -> + send_A(answer(T, Caps, Pkt, App, Dict0, RecvData), + TPid, + Dict0, + Pkt, + EvalPktFs, + EvalFs); + +send_A(_, _, _, _) -> + ok. + +%% send_A/6 + +send_A(T, TPid, Dict0, ReqPkt, EvalPktFs, EvalFs) -> + reply(T, TPid, Dict0, EvalPktFs, ReqPkt), + lists:foreach(fun diameter_lib:eval/1, EvalFs). + +%% answer/6 + +answer({reply, Ans}, _Caps, _Pkt, App, Dict0, _RecvData) -> + {dict(App#diameter_app.dictionary, Dict0, Ans), Ans}; + +answer({call, Opts}, Caps, Pkt, App, Dict0, RecvData) -> + #diameter_caps{origin_host = {OH,_}} + = Caps, + #diameter_packet{avps = Avps} + = Pkt, + {Code, _Flags, Vid} = Dict0:avp_header('Route-Record'), + resend(is_loop(Code, Vid, OH, Dict0, Avps), + Opts, + Caps, + Pkt, + App, + Dict0, + RecvData); + +%% RFC 3588 only allows 3xxx errors in an answer-message. RFC 6733 +%% added the possibility of setting 5xxx. +answer({answer_message, RC} = T, Caps, Pkt, App, Dict0, _RecvData) -> + Dict0 /= ?BASE orelse 3 == RC div 1000 + orelse ?ERROR({invalid_return, T, handle_request, App}), + answer_message(RC, Caps, Dict0, Pkt). + +%% dict/3 + +%% An incoming answer, not yet decoded. +dict(Dict, Dict0, #diameter_packet{header + = #diameter_header{is_request = false, + is_error = E}, + msg = undefined}) -> + if E -> Dict0; true -> Dict end; + +dict(Dict, Dict0, [Msg]) -> + dict(Dict, Dict0, Msg); + +dict(Dict, Dict0, #diameter_packet{msg = Msg}) -> + dict(Dict, Dict0, Msg); + +dict(Dict, Dict0, Msg) -> + choose(is_answer_message(Msg, Dict0), Dict0, Dict). + +is_answer_message([Name | _], _) -> + Name == 'answer-message'; + +is_answer_message(Rec, Dict) -> + try + 'answer-message' == Dict:rec2msg(element(1,Rec)) + catch + error:_ -> false + end. + +%% answer_message/4 + +answer_message(RC, + #diameter_caps{origin_host = {OH,_}, + origin_realm = {OR,_}}, + Dict0, + #diameter_packet{avps = Avps} + = Pkt) -> + ?LOG({error, RC}, Pkt), + {Dict0, answer_message(OH, OR, RC, Dict0, Avps)}. + +%% resend/7 + +%% DIAMETER_LOOP_DETECTED 3005 +%% An agent detected a loop while trying to get the message to the +%% intended recipient. The message MAY be sent to an alternate peer, +%% if one is available, but the peer reporting the error has +%% identified a configuration problem. + +resend(true, _Opts, Caps, Pkt, _App, Dict0, _RecvData) -> + answer_message(3005, Caps, Dict0, Pkt); + +%% 6.1.8. Relaying and Proxying Requests +%% +%% A relay or proxy agent MUST append a Route-Record AVP to all requests +%% forwarded. The AVP contains the identity of the peer the request was +%% received from. + +resend(false, + Opts, + #diameter_caps{origin_host = {_,OH}} + = Caps, + #diameter_packet{header = Hdr0, + avps = Avps} + = Pkt, + App, + Dict0, + #recvdata{service_name = SvcName, + sequence = Mask}) -> + Route = #diameter_avp{data = {Dict0, 'Route-Record', OH}}, + Seq = diameter_session:sequence(Mask), + Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq}, + Msg = [Hdr, Route | Avps], + resend(send_request(SvcName, App, Msg, Opts), Caps, Dict0, Pkt). +%% The incoming request is relayed with the addition of a +%% Route-Record. Note the requirement on the return from call/4 below, +%% which places a requirement on the value returned by the +%% handle_answer callback of the application module in question. +%% +%% Note that there's nothing stopping the request from being relayed +%% back to the sender. A pick_peer callback may want to avoid this but +%% a smart peer might recognize the potential loop and choose another +%% route. A less smart one will probably just relay the request back +%% again and force us to detect the loop. A pick_peer that wants to +%% avoid this can specify filter to avoid the possibility. +%% Eg. {neg, {host, OH} where #diameter_caps{origin_host = {OH, _}}. +%% +%% RFC 6.3 says that a relay agent does not modify Origin-Host but +%% says nothing about a proxy. Assume it should behave the same way. + +%% resend/4 +%% +%% Relay a reply to a relayed request. + +%% Answer from the peer: reset the hop by hop identifier and send. +resend(#diameter_packet{bin = B} + = Pkt, + _Caps, + _Dict0, + #diameter_packet{header = #diameter_header{hop_by_hop_id = Id}, + transport_data = TD}) -> + Pkt#diameter_packet{bin = diameter_codec:hop_by_hop_id(Id, B), + transport_data = TD}; +%% TODO: counters + +%% Or not: DIAMETER_UNABLE_TO_DELIVER. +resend(_, Caps, Dict0, Pkt) -> + answer_message(3002, Caps, Dict0, Pkt). + +%% is_loop/5 +%% +%% Is there a Route-Record AVP with our Origin-Host? + +is_loop(Code, + Vid, + Bin, + _Dict0, + [#diameter_avp{code = Code, vendor_id = Vid, data = Bin} | _]) -> + true; + +is_loop(_, _, _, _, []) -> + false; + +is_loop(Code, Vid, OH, Dict0, [_ | Avps]) + when is_binary(OH) -> + is_loop(Code, Vid, OH, Dict0, Avps); + +is_loop(Code, Vid, OH, Dict0, Avps) -> + is_loop(Code, Vid, Dict0:avp(encode, OH, 'Route-Record'), Dict0, Avps). + +%% reply/5 + +%% Local answer ... +reply({Dict, Ans}, TPid, Dict0, Fs, ReqPkt) -> + reply(Ans, Dict, TPid, Dict0, Fs, ReqPkt); + +%% ... or relayed. +reply(#diameter_packet{} = Pkt, TPid, _Dict0, Fs, _ReqPkt) -> + eval_packet(Pkt, Fs), + send(TPid, Pkt). + +%% reply/6 +%% +%% Send a locally originating reply. + +%% Skip the setting of Result-Code and Failed-AVP's below. This is +%% undocumented and shouldn't be relied on. +reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt) + when is_list(Msg); + 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), + incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes + send(TPid, Pkt). + +%% reset/2 + +%% Header/avps list: send as is. +reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _) -> + Pkt; + +%% No errors to set or errors explicitly ignored. +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(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. + +pacc({RC, #diameter_avp{} = A}, {E3, E5, Acc}) + when is_integer(RC) -> + pacc(RC, {E3, E5, [A|Acc]}); + +pacc(#diameter_avp{} = A, {E3, E5, Acc}) -> + {E3, E5, [A|Acc]}; + +pacc(RC, {false, E5, Acc}) + when 3 == RC div 1000 -> + {RC, E5, Acc}; + +pacc(RC, {E3, false, Acc}) + when 5 == RC div 1000 -> + {E3, RC, Acc}; + +pacc(_, Acc) -> + Acc. + +eval_packet(Pkt, Fs) -> + lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs). + +%% make_answer_packet/2 + +%% Use decode errors to set Result-Code and/or Failed-AVP unless the +%% the errors field has been explicitly set. Unfortunately, the +%% default value is the empty list rather than 'undefined' so use the +%% atom 'false' for "set nothing". (This is historical and changing +%% the default value would require modules including diameter.hrl to +%% be recompiled.) +make_answer_packet(#diameter_packet{errors = []} + = Pkt, + #diameter_packet{errors = [_|_] = Es} + = ReqPkt) -> + make_answer_packet(Pkt#diameter_packet{errors = Es}, ReqPkt); + +%% A reply message clears the R and T flags and retains the P flag. +%% The E flag will be set at encode. 6.2 of 3588 requires the same P +%% flag on an answer as on the request. A #diameter_packet{} returned +%% from a handle_request callback can circumvent this by setting its +%% own header values. +make_answer_packet(#diameter_packet{header = Hdr, + msg = Msg, + errors = Es, + transport_data = TD}, + #diameter_packet{header = ReqHdr}) -> + Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, + is_request = false, + is_error = undefined, + is_retransmitted = false}, + #diameter_packet{header = fold_record(Hdr0, Hdr), + msg = Msg, + errors = Es, + transport_data = TD}; + +%% Binaries and header/avp lists are sent as-is. +make_answer_packet(Bin, #diameter_packet{transport_data = TD}) + when is_binary(Bin) -> + #diameter_packet{bin = Bin, + transport_data = TD}; +make_answer_packet([#diameter_header{} | _] = Msg, + #diameter_packet{transport_data = TD}) -> + #diameter_packet{msg = Msg, + transport_data = TD}; + +%% Otherwise, preserve transport_data. +make_answer_packet(Msg, #diameter_packet{transport_data = TD} = Pkt) -> + make_answer_packet(#diameter_packet{msg = Msg, transport_data = TD}, Pkt). + +%% Reply as name and tuple list ... +set([_|_] = Ans, Avps, _) -> + Ans ++ Avps; %% Values nearer tail take precedence. + +%% ... or record. +set(Rec, Avps, Dict) -> + Dict:'#set-'(Avps, Rec). + +%% rc/3 +%% +%% Turn the result code into a list if its optional and only set it if +%% 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]; + {0,1} -> [{K, [RC]}]; + _ -> [] + end; + +rc(Rec, T, Dict) -> + rc([Dict:rec2msg(element(1, Rec))], T, Dict). + +%% failed_avp/3 + +failed_avp(_, [] = No, _) -> + No; + +failed_avp(Rec, Failed, Dict) -> + [fa(Rec, [{'AVP', Failed}], Dict)]. + +%% Reply as name and tuple list ... +fa([MsgName | Values], FailedAvp, Dict) -> + R = Dict:msg2rec(MsgName), + try + Dict:'#info-'(R, {index, 'Failed-AVP'}), + {'Failed-AVP', [FailedAvp]} + catch + error: _ -> + Avps = proplists:get_value('AVP', Values, []), + A = #diameter_avp{name = 'Failed-AVP', + value = FailedAvp}, + {'AVP', [A|Avps]} + end; + +%% ... or record. +fa(Rec, FailedAvp, Dict) -> + try + {'Failed-AVP', [FailedAvp]} + catch + error: _ -> + Avps = Dict:'get-'('AVP', Rec), + A = #diameter_avp{name = 'Failed-AVP', + value = FailedAvp}, + {'AVP', [A|Avps]} + end. + +%% 3. Diameter Header +%% +%% E(rror) - If set, the message contains a protocol error, +%% and the message will not conform to the ABNF +%% described for this command. Messages with the 'E' +%% bit set are commonly referred to as error +%% messages. This bit MUST NOT be set in request +%% messages. See Section 7.2. + +%% 3.2. Command Code ABNF specification +%% +%% e-bit = ", ERR" +%% ; If present, the 'E' bit in the Command +%% ; Flags is set, indicating that the answer +%% ; message contains a Result-Code AVP in +%% ; the "protocol error" class. + +%% 7.1.3. Protocol Errors +%% +%% Errors that fall within the Protocol Error category SHOULD be treated +%% on a per-hop basis, and Diameter proxies MAY attempt to correct the +%% error, if it is possible. Note that these and only these errors MUST +%% only be used in answer messages whose 'E' bit is set. + +%% Thus, only construct answers to protocol errors. Other errors +%% require an message-specific answer and must be handled by the +%% application. + +%% 6.2. Diameter Answer Processing +%% +%% When a request is locally processed, the following procedures MUST be +%% applied to create the associated answer, in addition to any +%% additional procedures that MAY be discussed in the Diameter +%% application defining the command: +%% +%% - The same Hop-by-Hop identifier in the request is used in the +%% answer. +%% +%% - The local host's identity is encoded in the Origin-Host AVP. +%% +%% - The Destination-Host and Destination-Realm AVPs MUST NOT be +%% present in the answer message. +%% +%% - The Result-Code AVP is added with its value indicating success or +%% failure. +%% +%% - If the Session-Id is present in the request, it MUST be included +%% in the answer. +%% +%% - Any Proxy-Info AVPs in the request MUST be added to the answer +%% message, in the same order they were present in the request. +%% +%% - The 'P' bit is set to the same value as the one in the request. +%% +%% - The same End-to-End identifier in the request is used in the +%% answer. +%% +%% Note that the error messages (see Section 7.3) are also subjected to +%% the above processing rules. + +%% 7.3. Error-Message AVP +%% +%% The Error-Message AVP (AVP Code 281) is of type UTF8String. It MAY +%% accompany a Result-Code AVP as a human readable error message. The +%% Error-Message AVP is not intended to be useful in real-time, and +%% SHOULD NOT be expected to be parsed by network entities. + +%% answer_message/5 + +answer_message(OH, OR, RC, Dict0, Avps) -> + {Code, _, Vid} = Dict0:avp_header('Session-Id'), + ['answer-message', {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Result-Code', RC} + | session_id(Code, Vid, Dict0, Avps)]. + +session_id(Code, Vid, Dict0, Avps) + when is_list(Avps) -> + try + {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps), + [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}] + catch + error: _ -> + [] + end. + +%% find_avp/3 + +find_avp(Code, Vid, Avps) + when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) -> + find(fun(A) -> is_avp(Code, Vid, A) end, Avps). + +%% The final argument here could be a list of AVP's, depending on the case, +%% but we're only searching at the top level. +is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) -> + true; +is_avp(_, _, _) -> + false. + +find(_, []) -> + false; +find(Pred, [H|T]) -> + case Pred(H) of + true -> + {value, H}; + false -> + find(Pred, T) + end. + +%% 7. Error Handling +%% +%% There are certain Result-Code AVP application errors that require +%% additional AVPs to be present in the answer. In these cases, the +%% Diameter node that sets the Result-Code AVP to indicate the error +%% MUST add the AVPs. Examples are: +%% +%% - An unrecognized AVP is received with the 'M' bit (Mandatory bit) +%% set, causes an answer to be sent with the Result-Code AVP set to +%% DIAMETER_AVP_UNSUPPORTED, and the Failed-AVP AVP containing the +%% offending AVP. +%% +%% - An AVP that is received with an unrecognized value causes an +%% answer to be returned with the Result-Code AVP set to +%% DIAMETER_INVALID_AVP_VALUE, with the Failed-AVP AVP containing the +%% AVP causing the error. +%% +%% - A command is received with an AVP that is omitted, yet is +%% mandatory according to the command's ABNF. The receiver issues an +%% answer with the Result-Code set to DIAMETER_MISSING_AVP, and +%% creates an AVP with the AVP Code and other fields set as expected +%% in the missing AVP. The created AVP is then added to the Failed- +%% AVP AVP. +%% +%% The Result-Code AVP describes the error that the Diameter node +%% encountered in its processing. In case there are multiple errors, +%% the Diameter node MUST report only the first error it encountered +%% (detected possibly in some implementation dependent order). The +%% specific errors that can be described by this AVP are described in +%% the following section. + +%% 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. +%% +%% The possible reasons for this AVP are the presence of an improperly +%% constructed AVP, an unsupported or unrecognized AVP, an invalid AVP +%% value, the omission of a required AVP, the presence of an explicitly +%% excluded AVP (see tables in Section 10), or the presence of two or +%% more occurrences of an AVP which is restricted to 0, 1, or 0-1 +%% occurrences. +%% +%% A Diameter message MAY contain one Failed-AVP AVP, containing the +%% entire AVP that could not be processed successfully. If the failure +%% reason is omission of a required AVP, an AVP with the missing AVP +%% code, the missing vendor id, and a zero filled payload of the minimum +%% required length for the omitted AVP will be added. + +%% incr/4 +%% +%% Increment a stats counter for result codes in incoming and outgoing +%% answers. + +%% Outgoing message as binary: don't count. (Sending binaries is only +%% partially supported.) +incr(_, #diameter_packet{msg = undefined}, _, _, _) -> + ok; + +%% Incoming with decode errors. +incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid, _) -> + incr(TPid, {diameter_codec:msg_id(H), D, error}); + +%% Incoming without errors or outgoing. Outgoing with encode errors +%% never gets here since encode fails. +incr(Dir, Pkt, Dict, TPid, Dict0) -> + #diameter_packet{header = #diameter_header{is_error = E} + = Hdr, + msg = Rec} + = Pkt, + + RC = int(get_avp_value(Dict, 'Result-Code', Rec)), + + %% Exit on an improper Result-Code. + is_result(RC, E, Dict0) + orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]), + + irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)). + +%% No E-bit: can't be 3xxx. +is_result(RC, false, _Dict0) -> + RC < 3000 orelse 4000 =< RC; + +%% E-bit in RFC 3588: only 3xxx. +is_result(RC, true, ?BASE) -> + 3000 =< RC andalso RC < 4000; + +%% E-bit in RFC 6733: 3xxx or 5xxx. +is_result(RC, true, _) -> + 3000 =< RC andalso RC < 4000 + orelse + 5000 =< RC andalso RC < 6000. + +irc(_, _, _, undefined) -> + false; + +irc(TPid, Hdr, Dir, Ctr) -> + incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}). + +%% incr/2 + +incr(TPid, Counter) -> + diameter_stats:incr(Counter, TPid, 1). + +%% rc_counter/2 + +%% RFC 3588, 7.6: +%% +%% All Diameter answer messages defined in vendor-specific +%% applications MUST include either one Result-Code AVP or one +%% Experimental-Result AVP. +%% +%% Maintain statistics assuming one or the other, not both, which is +%% surely the intent of the RFC. + +rc_counter(Dict, Rec, undefined) -> + rcc(get_avp_value(Dict, 'Experimental-Result', Rec)); +rc_counter(_, _, RC) -> + {'Result-Code', RC}. + +%% Outgoing answers may be in any of the forms messages can be sent +%% in. Incoming messages will be records. We're assuming here that the +%% arity of the result code AVP's is 0 or 1. + +rcc([{_,_,N} = T | _]) + when is_integer(N) -> + T; +rcc({_,_,N} = T) + when is_integer(N) -> + T; +rcc(_) -> + undefined. + +%% Extract the first good looking integer. There's no guarantee +%% that what we're looking for has arity 1. +int([N|_]) + when is_integer(N) -> + N; +int(N) + when is_integer(N) -> + N; +int(_) -> + undefined. + +-spec x(any(), atom(), list()) -> no_return(). + +%% Warn and exit request process on errors in an incoming answer. +x(Reason, F, A) -> + diameter_lib:warning_report(Reason, {?MODULE, F, A}), + x(Reason). + +x(T) -> + exit(T). + +%% --------------------------------------------------------------------------- +%% # send_request/4 +%% +%% Handle an outgoing Diameter request. +%% --------------------------------------------------------------------------- + +send_request(SvcName, AppOrAlias, Msg, Options) + when is_list(Options) -> + Rec = make_options(Options), + Ref = make_ref(), + Caller = {self(), Ref}, + ReqF = fun() -> + exit({Ref, send_R(SvcName, AppOrAlias, Msg, Rec, Caller)}) + end, + try spawn_monitor(ReqF) of + {_, MRef} -> + recv_A(MRef, Ref, Rec#options.detach, false) + catch + error: system_limit = E -> + {error, E} + end. +%% The R in send_R is because Diameter request are usually given short +%% names of the form XXR. (eg. CER, DWR, etc.) Similarly, answers have +%% names of the form XXA. + +%% Don't rely on gen_server:call/3 for the timeout handling since it +%% makes no guarantees about not leaving a reply message in the +%% mailbox if we catch its exit at timeout. It currently *can* do so, +%% which is also undocumented. + +recv_A(MRef, _, true, true) -> + erlang:demonitor(MRef, [flush]), + ok; + +recv_A(MRef, Ref, Detach, Sent) -> + receive + Ref -> %% send has been attempted + recv_A(MRef, Ref, Detach, true); + {'DOWN', MRef, process, _, Reason} -> + answer_rc(Reason, Ref, Sent) + end. + +%% send_R/5 has returned ... +answer_rc({Ref, Ans}, Ref, _) -> + Ans; + +%% ... or not. Note that failure/encode are documented return values. +answer_rc(_, _, Sent) -> + {error, choose(Sent, failure, encode)}. + +%% send_R/5 +%% +%% In the process spawned for the outgoing request. + +send_R(SvcName, AppOrAlias, Msg, Opts, Caller) -> + case pick_peer(SvcName, AppOrAlias, Msg, Opts) of + {{_,_,_} = Transport, Mask} -> + send_request(Transport, Mask, Msg, Opts, Caller, SvcName); + false -> + {error, no_connection}; + {error, _} = No -> + No + end. + +%% make_options/1 + +make_options(Options) -> + lists:foldl(fun mo/2, #options{}, Options). + +mo({timeout, T}, Rec) + when is_integer(T), 0 =< T -> + Rec#options{timeout = T}; + +mo({filter, F}, #options{filter = none} = Rec) -> + Rec#options{filter = F}; +mo({filter, F}, #options{filter = {all, Fs}} = Rec) -> + Rec#options{filter = {all, [F | Fs]}}; +mo({filter, F}, #options{filter = F0} = Rec) -> + Rec#options{filter = {all, [F0, F]}}; + +mo({extra, L}, #options{extra = X} = Rec) + when is_list(L) -> + Rec#options{extra = X ++ L}; + +mo(detach, Rec) -> + Rec#options{detach = true}; + +mo(T, _) -> + ?ERROR({invalid_option, T}). + +%% --------------------------------------------------------------------------- +%% # send_request/6 +%% --------------------------------------------------------------------------- + +%% Send an outgoing request in its dedicated process. +%% +%% Note that both encode of the outgoing request and of the received +%% answer happens in this process. It's also this process that replies +%% to the caller. The service process only handles the state-retaining +%% callbacks. +%% +%% The module field of the #diameter_app{} here includes any extra +%% arguments passed to diameter:call/4. + +send_request({TPid, Caps, App} + = Transport, + Mask, + Msg, + Opts, + Caller, + SvcName) -> + Pkt = make_prepare_packet(Mask, Msg), + + send_R(cb(App, prepare_request, [Pkt, SvcName, {TPid, Caps}]), + Pkt, + Transport, + Opts, + Caller, + SvcName, + []). + +send_R({send, Msg}, Pkt, Transport, Opts, Caller, SvcName, Fs) -> + send_R(make_request_packet(Msg, Pkt), + Transport, + Opts, + Caller, + SvcName, + Fs); + +send_R({discard, Reason} , _, _, _, _, _, _) -> + {error, Reason}; + +send_R(discard, _, _, _, _, _, _) -> + {error, discarded}; + +send_R({eval_packet, RC, F}, Pkt, T, Opts, Caller, SvcName, Fs) -> + send_R(RC, Pkt, T, Opts, Caller, SvcName, [F|Fs]); + +send_R(E, _, {_, _, App}, _, _, _, _) -> + ?ERROR({invalid_return, E, prepare_request, App}). + +%% make_prepare_packet/2 +%% +%% Turn an outgoing request as passed to call/4 into a diameter_packet +%% record in preparation for a prepare_request callback. + +make_prepare_packet(_, Bin) + when is_binary(Bin) -> + #diameter_packet{header = diameter_codec:decode_header(Bin), + bin = Bin}; + +make_prepare_packet(Mask, #diameter_packet{msg = [#diameter_header{} = Hdr + | Avps]} + = Pkt) -> + Pkt#diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]}; + +make_prepare_packet(Mask, #diameter_packet{header = Hdr} = Pkt) -> + Pkt#diameter_packet{header = make_prepare_header(Mask, Hdr)}; + +make_prepare_packet(Mask, Msg) -> + make_prepare_packet(Mask, #diameter_packet{msg = Msg}). + +%% make_prepare_header/2 + +make_prepare_header(Mask, undefined) -> + Seq = diameter_session:sequence(Mask), + make_prepare_header(#diameter_header{end_to_end_id = Seq, + hop_by_hop_id = Seq}); + +make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined, + hop_by_hop_id = undefined} + = H) -> + Seq = diameter_session:sequence(Mask), + make_prepare_header(H#diameter_header{end_to_end_id = Seq, + hop_by_hop_id = Seq}); + +make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined} = H) -> + Seq = diameter_session:sequence(Mask), + make_prepare_header(H#diameter_header{end_to_end_id = Seq}); + +make_prepare_header(Mask, #diameter_header{hop_by_hop_id = undefined} = H) -> + Seq = diameter_session:sequence(Mask), + make_prepare_header(H#diameter_header{hop_by_hop_id = Seq}); + +make_prepare_header(_, Hdr) -> + make_prepare_header(Hdr). + +%% make_prepare_header/1 + +make_prepare_header(#diameter_header{version = undefined} = Hdr) -> + make_prepare_header(Hdr#diameter_header{version = ?DIAMETER_VERSION}); + +make_prepare_header(#diameter_header{} = Hdr) -> + Hdr; + +make_prepare_header(T) -> + ?ERROR({invalid_header, T}). + +%% make_request_packet/2 +%% +%% Reconstruct a diameter_packet from the return value of +%% prepare_request or prepare_retransmit callback. + +make_request_packet(Bin, _) + when is_binary(Bin) -> + make_prepare_packet(false, Bin); + +make_request_packet(#diameter_packet{msg = [#diameter_header{} | _]} + = Pkt, + _) -> + Pkt; + +%% Returning a diameter_packet with no header from a prepare_request +%% or prepare_retransmit callback retains the header passed into it. +%% This is primarily so that the end to end and hop by hop identifiers +%% are retained. +make_request_packet(#diameter_packet{header = Hdr} = Pkt, + #diameter_packet{header = Hdr0}) -> + Pkt#diameter_packet{header = fold_record(Hdr0, Hdr)}; + +make_request_packet(Msg, Pkt) -> + Pkt#diameter_packet{msg = Msg}. + +%% fold_record/2 + +fold_record(undefined, R) -> + R; +fold_record(Rec, R) -> + diameter_lib:fold_tuple(2, Rec, R). + +%% send_R/6 + +send_R(Pkt0, + {TPid, Caps, #diameter_app{dictionary = Dict} = App}, + Opts, + {Pid, Ref}, + SvcName, + Fs) -> + Pkt = encode(Dict, Pkt0, Fs), + + #options{timeout = Timeout} + = Opts, + + Req = #request{ref = Ref, + caller = Pid, + handler = self(), + transport = TPid, + caps = Caps, + packet = Pkt0}, + + try + TRef = send_request(TPid, Pkt, Req, SvcName, Timeout), + Pid ! Ref, %% tell caller a send has been attempted + handle_answer(SvcName, + App, + recv_A(Timeout, SvcName, App, Opts, {TRef, Req})) + after + erase_requests(Pkt) + end. + +%% recv_A/5 + +recv_A(Timeout, SvcName, App, Opts, {TRef, #request{ref = Ref} = Req}) -> + %% Matching on TRef below ensures we ignore messages that pertain + %% to a previous transport prior to failover. The answer message + %% includes the #request{} since it's not necessarily Req; that + %% is, from the last peer to which we've transmitted. + receive + {answer = A, Ref, Rq, Dict0, Pkt} -> %% Answer from peer + {A, Rq, Dict0, Pkt}; + {timeout = Reason, TRef, _} -> %% No timely reply + {error, Req, Reason}; + {failover, TRef} -> %% Service says peer has gone down + retransmit(pick_peer(SvcName, App, Req, Opts), + Req, + Opts, + SvcName, + Timeout) + end. + +%% handle_answer/3 + +handle_answer(SvcName, App, {error, Req, Reason}) -> + handle_error(App, Req, Reason, SvcName); + +handle_answer(SvcName, + #diameter_app{dictionary = Dict, + id = Id} + = App, + {answer, Req, Dict0, Pkt}) -> + Mod = dict(Dict, Dict0, Pkt), + handle_A(errors(Id, diameter_codec:decode(Mod, Pkt)), + SvcName, + Mod, + Dict0, + App, + Req). + +%% We don't really need to do a full decode if we're a relay and will +%% just resend with a new hop by hop identifier, but might a proxy +%% want to examine the answer? + +handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> + try + incr(recv, Pkt, Dict, TPid, Dict0) %% count incoming result codes + of + _ -> answer(Pkt, SvcName, App, Req) + catch + exit: {invalid_error_bit, RC} -> + #diameter_packet{errors = Es} + = Pkt, + E = {5004, #diameter_avp{name = 'Result-Code', value = RC}}, + answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req) + end. + +answer(Pkt, + SvcName, + #diameter_app{module = ModX, + options = [{answer_errors, AE} | _]}, + Req) -> + a(Pkt, SvcName, ModX, AE, Req). + +a(#diameter_packet{errors = Es} + = Pkt, + SvcName, + ModX, + AE, + #request{transport = TPid, + caps = Caps, + packet = P}) + when [] == Es; + callback == AE -> + cb(ModX, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); + +a(Pkt, SvcName, _, report, Req) -> + x(errors, handle_answer, [SvcName, Req, Pkt]); + +a(Pkt, SvcName, _, discard, Req) -> + x({errors, handle_answer, [SvcName, Req, Pkt]}). + +%% Note that we don't check that the application id in the answer's +%% header is what we expect. (TODO: Does the rfc says anything about +%% this?) + +%% Note that failover starts a new timer and that expiry of an old +%% timer value is ignored. This means that an answer could be accepted +%% from a peer after timeout in the case of failover. + +retransmit({{_,_,App} = Transport, _Mask}, Req, Opts, SvcName, Timeout) -> + try retransmit(Transport, Req, SvcName, Timeout) of + T -> recv_A(Timeout, SvcName, App, Opts, T) + catch + ?FAILURE(Reason) -> {error, Req, Reason} + end; + +retransmit(_, Req, _, _, _) -> %% no alternate peer + {error, Req, failover}. + +%% pick_peer/4 + +%% Retransmission after failover: call-specific arguments have already +%% been appended in App. +pick_peer(SvcName, + App, + #request{packet = #diameter_packet{msg = Msg}}, + Opts) -> + pick_peer(SvcName, App, Msg, Opts#options{extra = []}); + +pick_peer(_, _, undefined, _) -> + false; + +pick_peer(SvcName, + AppOrAlias, + Msg, + #options{filter = Filter, extra = Xtra}) -> + diameter_service:pick_peer(SvcName, + AppOrAlias, + {fun(D) -> get_destination(D, Msg) end, + Filter, + Xtra}). + +%% handle_error/4 + +handle_error(App, + #request{packet = Pkt, + transport = TPid, + caps = Caps}, + Reason, + SvcName) -> + cb(App, handle_error, [Reason, msg(Pkt), SvcName, {TPid, Caps}]). + +msg(#diameter_packet{msg = undefined, bin = Bin}) -> + Bin; +msg(#diameter_packet{msg = Msg}) -> + Msg. + +%% encode/3 + +encode(Dict, Pkt, Fs) -> + P = encode(Dict, Pkt), + eval_packet(P, Fs), + P. + +%% encode/2 + +%% Note that prepare_request can return a diameter_packet containing a +%% header or transport_data. Even allow the returned record to contain +%% an encoded binary. This isn't the usual case and doesn't properly +%% support retransmission but is useful for test. + +%% A message to be encoded. +encode(Dict, #diameter_packet{bin = undefined} = Pkt) -> + diameter_codec:encode(Dict, Pkt); + +%% An encoded binary: just send. +encode(_, #diameter_packet{} = Pkt) -> + Pkt. + +%% send_request/5 + +send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, _SvcName, Timeout) + when node() == node(TPid) -> + %% Store the outgoing request before sending to avoid a race with + %% reply reception. + TRef = store_request(TPid, Bin, Req, Timeout), + send(TPid, Pkt), + TRef; + +%% Send using a remote transport: spawn a process on the remote node +%% to relay the answer. +send_request(TPid, #diameter_packet{} = Pkt, Req, SvcName, Timeout) -> + TRef = erlang:start_timer(Timeout, self(), TPid), + T = {TPid, Pkt, Req, SvcName, Timeout, TRef}, + spawn(node(TPid), ?MODULE, send, [T]), + TRef. + +%% send/1 + +send({TPid, Pkt, #request{handler = Pid} = Req, SvcName, Timeout, TRef}) -> + Ref = send_request(TPid, + Pkt, + Req#request{handler = self()}, + SvcName, + Timeout), + receive + {answer, _, _, _, _} = A -> + Pid ! A; + {failover = T, Ref} -> + Pid ! {T, TRef}; + T -> + exit({timeout, Ref, TPid} = T) + end. + +%% send/2 + +send(Pid, Pkt) -> + Pid ! {send, Pkt}. + +%% retransmit/4 + +retransmit({TPid, Caps, App} + = Transport, + #request{packet = Pkt0} + = Req, + SvcName, + Timeout) -> + have_request(Pkt0, TPid) %% Don't failover to a peer we've + andalso ?THROW(timeout), %% already sent to. + + #diameter_packet{header = Hdr0} = Pkt0, + Hdr = Hdr0#diameter_header{is_retransmitted = true}, + Pkt = Pkt0#diameter_packet{header = Hdr}, + + retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]), + Transport, + Req#request{packet = Pkt}, + SvcName, + Timeout, + []). + +retransmit({send, Msg}, + Transport, + #request{packet = Pkt} + = Req, + SvcName, + Timeout, + Fs) -> + resend_request(make_request_packet(Msg, Pkt), + Transport, + Req, + SvcName, + Timeout, + Fs); + +retransmit({discard, Reason}, _, _, _, _, _) -> + ?THROW(Reason); + +retransmit(discard, _, _, _, _, _) -> + ?THROW(discarded); + +retransmit({eval_packet, RC, F}, Transport, Req, SvcName, Timeout, Fs) -> + retransmit(RC, Transport, Req, SvcName, Timeout, [F|Fs]); + +retransmit(T, {_, _, App}, _, _, _, _) -> + ?ERROR({invalid_return, T, prepare_retransmit, App}). + +resend_request(Pkt0, + {TPid, Caps, #diameter_app{dictionary = Dict}}, + Req0, + SvcName, + Tmo, + Fs) -> + Pkt = encode(Dict, Pkt0, Fs), + + Req = Req0#request{transport = TPid, + packet = Pkt0, + caps = Caps}, + + ?LOG(retransmission, Req), + TRef = send_request(TPid, Pkt, Req, SvcName, Tmo), + {TRef, Req}. + +%% store_request/4 + +store_request(TPid, Bin, Req, Timeout) -> + Seqs = diameter_codec:sequence_numbers(Bin), + 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 + TRef. + +%% lookup_request/2 + +lookup_request(Msg, TPid) -> + Seqs = diameter_codec:sequence_numbers(Msg), + Spec = [{{Seqs, #request{transport = TPid, _ = '_'}, '_'}, + [], + ['$_']}], + case ets:select(?REQUEST_TABLE, Spec) of + [{_, Req, _}] -> + Req; + [] -> + false + end. + +%% erase_requests/1 + +erase_requests(Pkt) -> + ets:delete(?REQUEST_TABLE, diameter_codec:sequence_numbers(Pkt)). + +%% match_requests/1 + +match_requests(TPid) -> + Pat = {'_', #request{transport = TPid, _ = '_'}, '_'}, + ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}]). + +%% have_request/2 + +have_request(Pkt, TPid) -> + Seqs = diameter_codec:sequence_numbers(Pkt), + Pat = {Seqs, #request{transport = TPid, _ = '_'}, '_'}, + '$end_of_table' /= ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}], 1). + +%% --------------------------------------------------------------------------- +%% # failover/1-2 +%% --------------------------------------------------------------------------- + +failover(TPid) + when is_pid(TPid) -> + lists:foreach(fun failover/1, match_requests(TPid)); +%% Note that a request process can store its request after failover +%% notifications are sent here: store_request/4 sends the notification +%% in that case. + +%% Failover as a consequence of request_peer_down/1: inform the +%% request process. +failover({_, Req, TRef}) -> + #request{handler = Pid, + packet = #diameter_packet{msg = M}} + = Req, + M /= undefined andalso (Pid ! {failover, TRef}). +%% Failover is not performed when msg = binary() since sending +%% pre-encoded binaries is only partially supported. (Mostly for +%% test.) + +%% get_destination/2 + +get_destination(Dict, Msg) -> + [str(get_avp_value(Dict, D, Msg)) || D <- ['Destination-Realm', + 'Destination-Host']]. + +%% This is not entirely correct. The avp could have an arity 1, in +%% which case an empty list is a DiameterIdentity of length 0 rather +%% than the list of no values we treat it as by mapping to undefined. +%% This behaviour is documented. +str([]) -> + undefined; +str(T) -> + T. + +%% get_avp_value/3 +%% +%% Find an AVP in a message of one of three forms: +%% +%% - a message record (as generated from a .dia spec) or +%% - a list of an atom message name followed by 2-tuple, avp name/value pairs. +%% - a list of a #diameter_header{} followed by #diameter_avp{} records, +%% +%% In the first two forms a dictionary module is used at encode to +%% identify the type of the AVP and its arity in the message in +%% question. The third form allows messages to be sent as is, without +%% a dictionary, which is needed in the case of relay agents, for one. + +%% Messages will be header/avps list as a relay and the only AVP's we +%% look for are in the common dictionary. This is required since the +%% relay dictionary doesn't inherit the common dictionary (which maybe +%% it should). +get_avp_value(?RELAY, Name, Msg) -> + get_avp_value(?BASE, Name, Msg); + +%% Message sent as a header/avps list, probably a relay case but not +%% necessarily. +get_avp_value(Dict, Name, [#diameter_header{} | Avps]) -> + try + {Code, _, VId} = Dict:avp_header(Name), + [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) -> + C /= Code orelse V /= VId + end, + Avps), + avp_decode(Dict, Name, A) + catch + error: _ -> + undefined + end; + +%% Outgoing message as a name/values list. +get_avp_value(_, Name, [_MsgName | Avps]) -> + case lists:keyfind(Name, 1, Avps) of + {_, V} -> + V; + _ -> + undefined + end; + +%% Message is typically a record but not necessarily. +get_avp_value(Dict, Name, Rec) -> + try + Dict:'#get-'(Name, Rec) + catch + error:_ -> + undefined + end. + +avp_decode(Dict, Name, #diameter_avp{value = undefined, + data = Bin}) -> + Dict:avp(decode, Bin, Name); +avp_decode(_, _, #diameter_avp{value = V}) -> + V. + +cb(#diameter_app{module = [_|_] = M}, F, A) -> + eval(M, F, A); +cb([_|_] = M, F, A) -> + eval(M, F, A). + +eval([M|X], F, A) -> + apply(M, F, A ++ X). + +choose(true, X, _) -> X; +choose(false, _, X) -> X. diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index a5429c967c..82ca603cf3 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -45,6 +45,16 @@ -define(DEFAULT_TW_INIT, 30000). %% RFC 3539 ch 3.4.1 -define(NOMASK, {0,32}). %% default sequence mask +-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, @@ -52,37 +62,42 @@ 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 - tref :: reference(), %% reference for current watchdog timer - message_data, %% term passed into diameter_service with message + tref :: reference(), %% reference for current watchdog timer + dictionary :: module(), %% common dictionary + receive_data :: term(), + %% 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 %% %% Start a monitor before the watchdog is allowed to proceed to ensure %% that a failed capabilities exchange produces the desired exit %% reason. +%% --------------------------------------------------------------------------- --spec start(Type, {RecvData, [Opt], SvcName, SvcOpts, #diameter_service{}}) +-spec start(Type, {RecvData, [Opt], SvcOpts, #diameter_service{}}) -> {reference(), pid()} when Type :: {connect|accept, diameter:transport_ref()}, RecvData :: term(), Opt :: diameter:transport_opt(), - SvcOpts :: [diameter:service_opt()], - SvcName :: diameter:service_name(). + SvcOpts :: [diameter:service_opt()]. start({_,_} = Type, T) -> - Ref = make_ref(), - {ok, Pid} = diameter_watchdog_sup:start_child({Ref, {Type, self(), T}}), + Ack = make_ref(), + {ok, Pid} = diameter_watchdog_sup:start_child({Ack, Type, self(), T}), try {erlang:monitor(process, Pid), Pid} after - Pid ! Ref + send(Pid, Ack) end. start_link(T) -> @@ -101,38 +116,116 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({Ref, {_, Pid, _} = T}) -> - MRef = erlang:monitor(process, Pid), - receive - Ref -> - make_state(T); - {'DOWN', MRef, process, _, _} = D -> - exit({shutdown, D}) - end. - -make_state({T, Pid, {RecvData, - Opts, - SvcName, - SvcOpts, - #diameter_service{applications = Apps, - capabilities = Caps} - = Svc}}) -> +i({Ack, T, Pid, {RecvData, + Opts, + SvcOpts, + #diameter_service{applications = Apps, + capabilities = Caps} + = Svc}}) -> + erlang:monitor(process, Pid), + wait(Ack, Pid), random:seed(now()), putr(restart, {T, Opts, Svc}), %% save seeing it in trace putr(dwr, dwr(Caps)), %% {_,_} = Mask = proplists:get_value(sequence, SvcOpts), Restrict = proplists:get_value(restrict_connections, SvcOpts), Nodes = restrict_nodes(Restrict), + Dict0 = common_dictionary(Apps), #watchdog{parent = Pid, - transport = monitor(diameter_peer_fsm:start(T, - Opts, - {Mask, Nodes, Svc})), + transport = start(T, Opts, Mask, Nodes, Dict0, Svc), tw = proplists:get_value(watchdog_timer, Opts, ?DEFAULT_TW_INIT), - message_data = {RecvData, SvcName, Apps, Mask}, + 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 + Ref -> + ok; + {'DOWN', _, process, Pid, _} = D -> + 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). + +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, _) -> + config_error(T). + +%% start/5 + +start(T, Opts, Mask, Nodes, Dict0, Svc) -> + {_MRef, Pid} + = diameter_peer_fsm:start(T, Opts, {Mask, Nodes, Dict0, Svc}), + Pid. + +%% common_dictionary/1 +%% +%% Determine the dictionary of the Diameter common application with +%% Application Id 0. Fail on config errors. + +common_dictionary(Apps) -> + case + orddict:fold(fun dict0/3, + false, + lists:foldl(fun(#diameter_app{dictionary = M}, D) -> + orddict:append(M:id(), M, D) + end, + orddict:new(), + Apps)) + of + {value, Mod} -> + Mod; + false -> + %% A transport should configure a common dictionary but + %% don't require it. Not configuring a common dictionary + %% means a user won't be able either send of receive + %% messages in the common dictionary: incoming request + %% will be answered with 3007 and outgoing requests cannot + %% be sent. The dictionary returned here is oly used for + %% messages diameter sends and receives: CER/CEA, DPR/DPA + %% and DWR/DWA. + ?BASE + end. + +%% Each application should be represented by a single dictionary. +dict0(Id, [_,_|_] = Ms, _) -> + config_error({multiple_dictionaries, Ms, {application_id, Id}}); + +%% An explicit common dictionary. +dict0(?APP_ID_COMMON, [Mod], _) -> + {value, Mod}; + +%% A pure relay, in which case the common application is implicit. +%% This uses the fact that the common application will already have +%% been folded. +dict0(?APP_ID_RELAY, _, false) -> + {value, ?BASE}; + +dict0(_, _, Acc) -> + Acc. + +config_error(T) -> + ?ERROR({configuration_error, T}). %% handle_call/3 @@ -151,41 +244,70 @@ handle_info(T, #watchdog{} = State) -> ok -> {noreply, State}; #watchdog{} = S -> - event(State, S), + close(T, State), %% service expects 'close' message + event(T, State, S), %% before 'watchdog' {noreply, S}; stop -> ?LOG(stop, T), - event(State, State#watchdog{status = down}), + event(T, State, State#watchdog{status = down}), {stop, {shutdown, T}, State} end; -handle_info(T, S) -> - handle_info(T, upgrade(S)). +handle_info(T, State) -> %% started in old code + handle_info(T, upgrade(State)). -upgrade(S) -> - #watchdog{} = list_to_tuple(tuple_to_list(S) - ++ [?NOMASK, {nodes, true}, false]). +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}}, + #watchdog{transport = TPid, + parent = Pid}) -> + send(Pid, {close, self(), Reason}); + +close(_, _) -> + ok. -event(#watchdog{status = T}, #watchdog{status = T}) -> +event(_, #watchdog{status = T}, #watchdog{status = T}) -> ok; -event(#watchdog{transport = undefined}, #watchdog{transport = undefined}) -> +event(_, #watchdog{transport = undefined}, #watchdog{transport = undefined}) -> ok; -event(#watchdog{status = From, transport = F, parent = Pid}, +event(Msg, + #watchdog{status = From, transport = F, parent = Pid}, #watchdog{status = To, transport = T}) -> - E = {tpid(F,T), From, To}, - notify(Pid, E), + TPid = tpid(F,T), + E = {[TPid | data(Msg, TPid, From, To)], From, To}, + send(Pid, {watchdog, self(), E}), ?LOG(transition, {self(), E}). +data(Msg, TPid, reopen, okay) -> + {recv, TPid, 'DWA', _Pkt} = Msg, %% assert + {TPid, T} = eraser(open), + [T]; + +data({open, TPid, _Hosts, T}, TPid, _From, To) + when To == okay; + To == reopen -> + [T]; + +data(_, _, _, _) -> + []. + tpid(_, Pid) when is_pid(Pid) -> Pid; + tpid(Pid, _) -> Pid. -notify(Pid, E) -> - Pid ! {watchdog, self(), E}. +send(Pid, T) -> + Pid ! T. %% terminate/2 @@ -215,15 +337,13 @@ transition(close, #watchdog{}) -> ok; %% Service is asking for the peer to be taken down gracefully. -transition({shutdown, Pid}, #watchdog{parent = Pid, - transport = undefined, - status = S}) -> - down = S, %% sanity check +transition({shutdown, Pid, _}, #watchdog{parent = Pid, + transport = undefined}) -> stop; -transition({shutdown = T, Pid}, #watchdog{parent = Pid, - transport = TPid} - = S) -> - TPid ! {T, self()}, +transition({shutdown = T, Pid, Reason}, #watchdog{parent = Pid, + transport = TPid} + = S) -> + send(TPid, {T, self(), Reason}), S#watchdog{shutdown = true}; %% Parent process has died, @@ -234,13 +354,9 @@ transition({'DOWN', _, process, Pid, _Reason}, %% Transport has accepted a connection. transition({accepted = T, TPid}, #watchdog{transport = TPid, parent = Pid}) -> - Pid ! {T, self(), TPid}, + send(Pid, {T, self(), TPid}), ok; -%% Transport is telling us that its impending death isn't failure. -transition({close, TPid, _Reason}, #watchdog{transport = TPid}) -> - stop; - %% STATE Event Actions New State %% ===== ------ ------- ---------- %% INITIAL Connection up SetWatchdog() OKAY @@ -255,16 +371,16 @@ transition({close, TPid, _Reason}, #watchdog{transport = TPid}) -> %% know the identity of the peer (ie. now) that we know that we're in %% state down rather than initial. -transition({open, TPid, Hosts, T} = Open, +transition({open, TPid, Hosts, _} = Open, #watchdog{transport = TPid, status = initial, - parent = Pid, - restrict = {_, R}} + restrict = {_,R}, + config = #config{suspect = OS}} = S) -> case okay(getr(restart), Hosts, R) of okay -> - open(Pid, {TPid, T}), - set_watchdog(S#watchdog{status = okay}); + set_watchdog(S#watchdog{status = okay, + num_dwa = OS}); reopen -> transition(Open, S#watchdog{status = down}) end; @@ -274,19 +390,24 @@ transition({open, TPid, Hosts, T} = Open, %% SetWatchdog() %% Pending = TRUE REOPEN -transition({open = P, TPid, _Hosts, T}, +transition({open = Key, TPid, _Hosts, T}, #watchdog{transport = TPid, - parent = Pid, - 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(P, {TPid, T}), - Pid ! {reopen, self(), {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() @@ -296,26 +417,18 @@ transition({open = P, TPid, _Hosts, T}, %% REOPEN Connection down CloseConnection() %% SetWatchdog() DOWN -transition({'DOWN', _, process, TPid, _}, +transition({'DOWN', _, process, TPid, _Reason}, #watchdog{transport = TPid, - status = S, - shutdown = D}) - when S == initial; - D -> + shutdown = true}) -> stop; -transition({'DOWN', _, process, TPid, _}, - #watchdog{transport = TPid} +transition({'DOWN', _, process, TPid, _Reason}, + #watchdog{transport = TPid, + status = T} = S) -> - failover(S), - close(S), - set_watchdog(S#watchdog{status = down, + set_watchdog(S#watchdog{status = ?CHOOSE(initial == T, T, down), pending = false, transport = undefined}); -%% Any outstanding pending (or other messages from the transport) will -%% have arrived before 'DOWN' since the message comes from the same -%% process. Note that we could also get this message in the initial -%% state. %% Incoming message. transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) -> @@ -331,15 +444,11 @@ transition({timeout, _, tw}, #watchdog{}) -> %% State query. transition({state, Pid}, #watchdog{status = S}) -> - Pid ! {self(), S}, + send(Pid, {self(), S}), ok. %% =========================================================================== -monitor(Pid) -> - erlang:monitor(process, Pid), - Pid. - putr(Key, Val) -> put({?MODULE, Key}, Val). @@ -349,16 +458,16 @@ getr(Key) -> eraser(Key) -> erase({?MODULE, Key}). -%% encode/2 +%% encode/3 -encode(Msg, Mask) -> +encode(Msg, Mask, Dict) -> Seq = diameter_session:sequence(Mask), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, Pkt = #diameter_packet{header = Hdr, msg = Msg}, - #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Pkt), + #diameter_packet{bin = Bin} = diameter_codec:encode(Dict, Pkt), Bin. %% okay/3 @@ -386,7 +495,7 @@ okay([{_,P}]) -> %% ... or it has. okay(C) -> - [_|_] = [P ! close || {_,P} <- C, self() /= P], + [_|_] = [send(P, close) || {_,P} <- C, self() /= P], reopen. %% set_watchdog/1 @@ -408,36 +517,14 @@ tw(T) tw({M,F,A}) -> apply(M,F,A). -%% open/2 - -open(Pid, {_,_} = T) -> - Pid ! {connection_up, self(), T}. - -%% failover/1 - -failover(#watchdog{status = okay, - parent = Pid}) -> - Pid ! {connection_down, self()}; - -failover(_) -> - ok. - -%% close/1 - -close(#watchdog{status = down}) -> - ok; - -close(#watchdog{parent = Pid}) -> - {{T, _}, _, _} = getr(restart), - T == accept andalso (Pid ! {close, self()}). - %% send_watchdog/1 send_watchdog(#watchdog{pending = false, transport = TPid, + dictionary = Dict0, sequence = Mask} = S) -> - TPid ! {send, encode(getr(dwr), Mask)}, + send(TPid, {send, encode(getr(dwr), Mask, Dict0)}), ?LOG(send, 'DWR'), S#watchdog{pending = true}. @@ -465,8 +552,9 @@ rcv(N, _, _) false; rcv(_, Pkt, #watchdog{transport = TPid, - message_data = T}) -> - diameter_service:receive_message(TPid, Pkt, T). + dictionary = Dict0, + receive_data = T}) -> + diameter_traffic:receive_message(TPid, Pkt, Dict0, T). throwaway(S) -> throw({?MODULE, throwaway, S}). @@ -517,26 +605,27 @@ rcv(_, #watchdog{status = okay} = S) -> %% SUSPECT Receive non-DWA Failback() %% SetWatchdog() OKAY -rcv('DWA', #watchdog{status = suspect} = S) -> - failback(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) -> - failback(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, - parent = Pid} - = S) -> - open(Pid, eraser(open)), + 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 @@ -553,11 +642,6 @@ rcv('DWA', #watchdog{status = reopen, rcv(_, #watchdog{status = reopen} = S) -> throwaway(S). -%% failback/1 - -failback(#watchdog{parent = Pid}) -> - Pid ! {connection_up, self()}. - %% timeout/1 %% %% The caller sets the watchdog on the return value. @@ -580,10 +664,17 @@ timeout(#watchdog{status = T, %% Pending SetWatchdog() SUSPECT timeout(#watchdog{status = okay, - pending = true} - = S) -> - failover(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 @@ -599,7 +690,6 @@ timeout(#watchdog{status = T, when T == suspect; T == reopen, P, N < 0 -> exit(TPid, {shutdown, watchdog_timeout}), - close(S), S#watchdog{status = down}; %% REOPEN Timer expires & NumDWA = -1 @@ -633,7 +723,9 @@ timeout(#watchdog{status = reopen, %% process has died. We only need to handle state down since we start %% the first watchdog when transitioning out of initial. -timeout(#watchdog{status = down} = S) -> +timeout(#watchdog{status = T} = S) + when T == initial; + T == down -> restart(S). %% restart/1 @@ -655,15 +747,15 @@ restart(S) -> %% state down rather then initial when receiving notification of an %% open connection. -restart({{connect, _} = T, Opts, Svc}, #watchdog{parent = Pid, - sequence = Mask, - restrict = {R,_}} - = S) -> - Pid ! {reconnect, self()}, +restart({{connect, _} = T, Opts, Svc}, + #watchdog{parent = Pid, + sequence = Mask, + restrict = {R,_}, + dictionary = Dict0} + = S) -> + send(Pid, {reconnect, self()}), Nodes = restrict_nodes(R), - S#watchdog{transport = monitor(diameter_peer_fsm:start(T, - Opts, - {Mask, Nodes, Svc})), + S#watchdog{transport = start(T, Opts, Mask, Nodes, Dict0, Svc), restrict = {R, lists:member(node(), Nodes)}}; %% No restriction on the number of connections to the same peer: just diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 1e31c40afe..80036879ea 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.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 @@ -128,8 +128,8 @@ gen(hrl, Spec, Mod, Path) -> gen(erl, Spec, Mod, Path) -> Forms = [{?attribute, module, Mod}, - {?attribute, compile, [{parse_transform, diameter_exprecs}]}, - {?attribute, compile, [{parse_transform, diameter_nowarn}]}, + {?attribute, compile, {parse_transform, diameter_exprecs}}, + {?attribute, compile, nowarn_unused_function}, {?attribute, export, [{name, 0}, {id, 0}, {vendor_id, 0}, diff --git a/lib/diameter/src/compiler/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl index 191f53f29d..9e32f53724 100644 --- a/lib/diameter/src/compiler/diameter_exprecs.erl +++ b/lib/diameter/src/compiler/diameter_exprecs.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 @@ -18,7 +18,7 @@ %% %% -%% Parse transform for generating record access functions +%% Parse transform for generating record access functions. %% %% This parse transform can be used to reduce compile-time %% dependencies in large systems. @@ -39,21 +39,21 @@ %% %% export_records([RecName, ...]) %% -%% causes this transform to lay out access functions for the exported -%% records: +%% causes this transform to insert functions for the exported records: %% %% -module(foo) %% -compile({parse_transform, diameter_exprecs}). %% %% -record(r, {a, b, c}). -%% -export_records([a]). +%% -export_records([r]). %% %% -export(['#info-'/1, '#info-'/2, -%% '#new-'/1, '#new-'/2, -%% '#get-'/2, '#set-'/2, -%% '#new-a'/0, '#new-a'/1, -%% '#get-a'/2, '#set-a'/2, -%% '#info-a'/1]). +%% '#new-'/1, '#new-'/2, +%% '#get-'/1', '#get-'/2, +%% '#set-'/2, +%% '#new-r'/0, '#new-r'/1, +%% '#get-r'/2, '#set-r'/2, +%% '#info-r'/1]). %% %% '#info-'(RecName) -> %% '#info-'(RecName, fields). @@ -61,15 +61,23 @@ %% '#info-'(r, Info) -> %% '#info-r'(Info). %% +%% '#new-'([r | Vals]) -> '#new-r'(Vals); %% '#new-'(r) -> #r{}. -%% '#new-'(r, Vals) -> '#new-r'(Vals) +%% +%% '#new-'(r, Vals) -> '#new-r'(Vals). %% %% '#new-r'() -> #r{}. %% '#new-r'(Vals) -> '#set-r'(Vals, #r{}). %% +%% '#get-'(#r{} = Rec) -> +%% [r | '#get-r'(Rec)]. +%% %% '#get-'(Attrs, #r{} = Rec) -> %% '#get-r'(Attrs, Rec). %% +%% '#get-r'(#r{} = Rec) -> +%% lists:zip([a,b,c], tl(tuple_to_list(Rec))). +%% %% '#get-r'(Attrs, Rec) when is_list(Attrs) -> %% ['#get-r'(A, Rec) || A <- Attrs]; %% '#get-r'(a, Rec) -> Rec#r.a; @@ -116,6 +124,7 @@ a_export(Exports) -> {fname(info), 2}, {fname(new), 1}, {fname(new), 2}, + {fname(get), 1}, {fname(get), 2}, {fname(set), 2} | lists:flatmap(fun export/1, Exports)]}. @@ -124,6 +133,7 @@ export(Rname) -> New = fname(new, Rname), [{New, 0}, {New, 1}, + {fname(get, Rname), 1}, {fname(get, Rname), 2}, {fname(set, Rname), 2}, {fname(info, Rname), 1}]. @@ -135,6 +145,7 @@ f_accessors(Es, Rs) -> '#info-/2'(Es), '#new-/1'(Es), '#new-/2'(Es), + '#get-/1'(Es), '#get-/2'(Es), '#set-/2'(Es) | lists:flatmap(fun(N) -> accessors(N, fields(N, Rs)) end, Es)]. @@ -142,6 +153,7 @@ f_accessors(Es, Rs) -> accessors(Rname, Fields) -> ['#new-X/0'(Rname), '#new-X/1'(Rname), + '#get-X/1'(Rname, Fields), '#get-X/2'(Rname, Fields), '#set-X/2'(Rname, Fields), '#info-X/1'(Rname, Fields)]. @@ -183,12 +195,15 @@ fname(Op, Rname) -> '#new-/1'(Exports) -> {?function, fname(new), 1, - lists:map(fun 'new-'/1, Exports) ++ [?BADARG(1)]}. + lists:flatmap(fun 'new-'/1, Exports) ++ [?BADARG(1)]}. 'new-'(R) -> - {?clause, [?ATOM(R)], - [], - [{?record, R, []}]}. + [{?clause, [?ATOM(R)], + [], + [{?record, R, []}]}, + {?clause, [{?cons, ?ATOM(R), ?VAR('Vals')}], + [], + [?CALL(fname(new, R), [?VAR('Vals')])]}]. '#new-/2'(Exports) -> {?function, fname(new), 2, @@ -199,6 +214,15 @@ fname(Op, Rname) -> [], [?CALL(fname(new, R), [?VAR('Vals')])]}. +'#get-/1'(Exports) -> + {?function, fname(get), 1, + lists:map(fun 'get--'/1, Exports) ++ [?BADARG(1)]}. + +'get--'(R) -> + {?clause, [{?match, {?record, R, []}, ?VAR('Rec')}], + [], + [{?cons, ?ATOM(R), ?CALL(fname(get, R), [?VAR('Rec')])}]}. + '#get-/2'(Exports) -> {?function, fname(get), 2, lists:map(fun 'get-'/1, Exports) ++ [?BADARG(2)]}. @@ -245,6 +269,14 @@ fname(Op, Rname) -> [{?record, ?VAR('Rec'), Rname, [{?record_field, ?ATOM(Attr), ?VAR('V')}]}]}. +'#get-X/1'(Rname, Fields) -> + FName = fname(get, Rname), + Values = ?CALL(tl, [?CALL(tuple_to_list, [?VAR('Rec')])]), + {?function, FName, 1, + [{?clause, [?VAR('Rec')], + [], + [?APPLY(lists, zip, [?TERM(Fields), Values])]}]}. + '#get-X/2'(Rname, Fields) -> FName = fname(get, Rname), {?function, FName, 2, diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl index 4cd86c32aa..9b14c1715a 100644 --- a/lib/diameter/src/compiler/diameter_forms.hrl +++ b/lib/diameter/src/compiler/diameter_forms.hrl @@ -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 @@ -define(clause, ?F(clause)). -define(function, ?F(function)). -define(call, ?F(call)). +-define(cons, ?F(cons)). -define('fun', ?F('fun')). -define(generate, ?F(generate)). -define(lc, ?F(lc)). diff --git a/lib/diameter/src/compiler/diameter_nowarn.erl b/lib/diameter/src/compiler/diameter_nowarn.erl deleted file mode 100644 index 6c17af6563..0000000000 --- a/lib/diameter/src/compiler/diameter_nowarn.erl +++ /dev/null @@ -1,41 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2011. 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% -%% - -%% -%% A parse transform to work around dialyzer currently not -%% understanding nowarn_unused_function except on individual -%% functions. The include of diameter_gen.hrl by generated dictionary -%% modules contains code that may not be called depending on the -%% dictionary. (The relay dictionary for example.) -%% -%% Even called functions may contain cases that aren't used for a -%% particular dictionary. This also causes dialyzer to complain but -%% there's no way to silence it in this case. -%% - --module(diameter_nowarn). - --export([parse_transform/2]). - -parse_transform(Forms, _Options) -> - [{attribute, ?LINE, compile, {nowarn_unused_function, {F,A}}} - || {function, _, F, A, _} <- Forms] - ++ Forms. -%% Note that dialyzer also doesn't understand {nowarn_unused_function, FAs} -%% with FAs a list of tuples. diff --git a/lib/diameter/src/dict/acct_rfc6733.dia b/lib/diameter/src/dict/acct_rfc6733.dia new file mode 100644 index 0000000000..7d6d11a71e --- /dev/null +++ b/lib/diameter/src/dict/acct_rfc6733.dia @@ -0,0 +1,72 @@ +;; +;; %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% +;; + +@id 3 +@name diameter_gen_acct_rfc6733 +@prefix diameter_base_accounting +@vendor 0 IETF + +@inherits diameter_gen_base_rfc6733 + +@messages + + ACR ::= < Diameter Header: 271, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Destination-Host ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ACA ::= < Diameter Header: 271, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ AVP ] diff --git a/lib/diameter/src/dict/base_rfc6733.dia b/lib/diameter/src/dict/base_rfc6733.dia new file mode 100644 index 0000000000..e1d1f18d86 --- /dev/null +++ b/lib/diameter/src/dict/base_rfc6733.dia @@ -0,0 +1,415 @@ +;; +;; %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% +;; + +@id 0 +@name diameter_gen_base_rfc6733 +@prefix diameter_base +@vendor 0 IETF + +@avp_types + + Acct-Interim-Interval 85 Unsigned32 M + Accounting-Realtime-Required 483 Enumerated M + Acct-Multi-Session-Id 50 UTF8String M + Accounting-Record-Number 485 Unsigned32 M + Accounting-Record-Type 480 Enumerated M + Acct-Session-Id 44 OctetString M + Accounting-Sub-Session-Id 287 Unsigned64 M + Acct-Application-Id 259 Unsigned32 M + Auth-Application-Id 258 Unsigned32 M + Auth-Request-Type 274 Enumerated M + Authorization-Lifetime 291 Unsigned32 M + Auth-Grace-Period 276 Unsigned32 M + Auth-Session-State 277 Enumerated M + Re-Auth-Request-Type 285 Enumerated M + Class 25 OctetString M + Destination-Host 293 DiamIdent M + Destination-Realm 283 DiamIdent M + Disconnect-Cause 273 Enumerated M + Error-Message 281 UTF8String - + Error-Reporting-Host 294 DiamIdent - + Event-Timestamp 55 Time M + Experimental-Result 297 Grouped M + Experimental-Result-Code 298 Unsigned32 M + Failed-AVP 279 Grouped M + Firmware-Revision 267 Unsigned32 - + Host-IP-Address 257 Address M + Inband-Security-Id 299 Unsigned32 M + Multi-Round-Time-Out 272 Unsigned32 M + Origin-Host 264 DiamIdent M + Origin-Realm 296 DiamIdent M + Origin-State-Id 278 Unsigned32 M + Product-Name 269 UTF8String - + Proxy-Host 280 DiamIdent M + Proxy-Info 284 Grouped M + Proxy-State 33 OctetString M + Redirect-Host 292 DiamURI M + Redirect-Host-Usage 261 Enumerated M + Redirect-Max-Cache-Time 262 Unsigned32 M + Result-Code 268 Unsigned32 M + Route-Record 282 DiamIdent M + Session-Id 263 UTF8String M + Session-Timeout 27 Unsigned32 M + Session-Binding 270 Unsigned32 M + Session-Server-Failover 271 Enumerated M + Supported-Vendor-Id 265 Unsigned32 M + Termination-Cause 295 Enumerated M + User-Name 1 UTF8String M + Vendor-Id 266 Unsigned32 M + Vendor-Specific-Application-Id 260 Grouped M + +@messages + + CER ::= < Diameter Header: 257, REQ > + { Origin-Host } + { Origin-Realm } + 1* { Host-IP-Address } + { Vendor-Id } + { Product-Name } + [ Origin-State-Id ] + * [ Supported-Vendor-Id ] + * [ Auth-Application-Id ] + * [ Inband-Security-Id ] + * [ Acct-Application-Id ] + * [ Vendor-Specific-Application-Id ] + [ Firmware-Revision ] + * [ AVP ] + + CEA ::= < Diameter Header: 257 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + 1* { Host-IP-Address } + { Vendor-Id } + { Product-Name } + [ Origin-State-Id ] + [ Error-Message ] + [ Failed-AVP ] + * [ Supported-Vendor-Id ] + * [ Auth-Application-Id ] + * [ Inband-Security-Id ] + * [ Acct-Application-Id ] + * [ Vendor-Specific-Application-Id ] + [ Firmware-Revision ] + * [ AVP ] + + DPR ::= < Diameter Header: 282, REQ > + { Origin-Host } + { Origin-Realm } + { Disconnect-Cause } + * [ AVP ] + + DPA ::= < Diameter Header: 282 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Error-Message ] + [ Failed-AVP ] + * [ AVP ] + + DWR ::= < Diameter Header: 280, REQ > + { Origin-Host } + { Origin-Realm } + [ Origin-State-Id ] + * [ AVP ] + + DWA ::= < Diameter Header: 280 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Error-Message ] + [ Failed-AVP ] + [ Origin-State-Id ] + * [ AVP ] + + answer-message ::= < Diameter Header: code, ERR [PXY] > + 0*1 < Session-Id > + { Origin-Host } + { Origin-Realm } + { Result-Code } + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Experimental-Result ] + * [ Proxy-Info ] + * [ AVP ] + + RAR ::= < Diameter Header: 258, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + { Re-Auth-Request-Type } + [ User-Name ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + RAA ::= < Diameter Header: 258, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + STR ::= < Diameter Header: 275, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Application-Id } + { Termination-Cause } + [ User-Name ] + [ Destination-Host ] + * [ Class ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + STA ::= < Diameter Header: 275, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + * [ Class ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Origin-State-Id ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + ASR ::= < Diameter Header: 274, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + [ User-Name ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ASA ::= < Diameter Header: 274, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + ACR ::= < Diameter Header: 271, REQ, PXY > + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Destination-Host ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ACA ::= < Diameter Header: 271, PXY > + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Failed-AVP ] + [ Acct-Interim-Interval ] + [ Accounting-Realtime-Required ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Proxy-Info ] + * [ AVP ] + +@enum Disconnect-Cause + + REBOOTING 0 + BUSY 1 + DO_NOT_WANT_TO_TALK_TO_YOU 2 + +@enum Redirect-Host-Usage + + DONT_CACHE 0 + ALL_SESSION 1 + ALL_REALM 2 + REALM_AND_APPLICATION 3 + ALL_APPLICATION 4 + ALL_HOST 5 + ALL_USER 6 + +@enum Auth-Request-Type + + AUTHENTICATE_ONLY 1 + AUTHORIZE_ONLY 2 + AUTHORIZE_AUTHENTICATE 3 + +@enum Auth-Session-State + + STATE_MAINTAINED 0 + NO_STATE_MAINTAINED 1 + +@enum Re-Auth-Request-Type + + AUTHORIZE_ONLY 0 + AUTHORIZE_AUTHENTICATE 1 + +@enum Termination-Cause + + LOGOUT 1 + SERVICE_NOT_PROVIDED 2 + BAD_ANSWER 3 + ADMINISTRATIVE 4 + LINK_BROKEN 5 + AUTH_EXPIRED 6 + USER_MOVED 7 + SESSION_TIMEOUT 8 + +@enum Session-Server-Failover + + REFUSE_SERVICE 0 + TRY_AGAIN 1 + ALLOW_SERVICE 2 + TRY_AGAIN_ALLOW_SERVICE 3 + +@enum Accounting-Record-Type + + EVENT_RECORD 1 + START_RECORD 2 + INTERIM_RECORD 3 + STOP_RECORD 4 + +@enum Accounting-Realtime-Required + + DELIVER_AND_GRANT 1 + GRANT_AND_STORE 2 + GRANT_AND_LOSE 3 + +@define Result-Code + + ;; 7.1.1. Informational + MULTI_ROUND_AUTH 1001 + + ;; 7.1.2. Success + SUCCESS 2001 + LIMITED_SUCCESS 2002 + + ;; 7.1.3. Protocol Errors + COMMAND_UNSUPPORTED 3001 + UNABLE_TO_DELIVER 3002 + REALM_NOT_SERVED 3003 + TOO_BUSY 3004 + LOOP_DETECTED 3005 + REDIRECT_INDICATION 3006 + APPLICATION_UNSUPPORTED 3007 + INVALID_HDR_BITS 3008 + INVALID_AVP_BITS 3009 + UNKNOWN_PEER 3010 + + ;; 7.1.4. Transient Failures + AUTHENTICATION_REJECTED 4001 + OUT_OF_SPACE 4002 + ELECTION_LOST 4003 + + ;; 7.1.5. Permanent Failures + AVP_UNSUPPORTED 5001 + UNKNOWN_SESSION_ID 5002 + AUTHORIZATION_REJECTED 5003 + INVALID_AVP_VALUE 5004 + MISSING_AVP 5005 + RESOURCES_EXCEEDED 5006 + CONTRADICTING_AVPS 5007 + AVP_NOT_ALLOWED 5008 + AVP_OCCURS_TOO_MANY_TIMES 5009 + NO_COMMON_APPLICATION 5010 + UNSUPPORTED_VERSION 5011 + UNABLE_TO_COMPLY 5012 + INVALID_BIT_IN_HEADER 5013 + INVALID_AVP_LENGTH 5014 + INVALID_MESSAGE_LENGTH 5015 + INVALID_AVP_BIT_COMBO 5016 + NO_COMMON_SECURITY 5017 + +@grouped + + Proxy-Info ::= < AVP Header: 284 > + { Proxy-Host } + { Proxy-State } + * [ AVP ] + + Failed-AVP ::= < AVP Header: 279 > + 1* {AVP} + + Experimental-Result ::= < AVP Header: 297 > + { Vendor-Id } + { Experimental-Result-Code } + + Vendor-Specific-Application-Id ::= < AVP Header: 260 > + { Vendor-Id } + [ Auth-Application-Id ] + [ Acct-Application-Id ] diff --git a/lib/diameter/src/dict/capup_rfc6737.dia b/lib/diameter/src/dict/capup_rfc6737.dia new file mode 100644 index 0000000000..35d2a9f218 --- /dev/null +++ b/lib/diameter/src/dict/capup_rfc6737.dia @@ -0,0 +1,49 @@ +;; +;; %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% +;; + +@id 10 +@name diameter_gen_capup_rfc6737 +@prefix diameter_capup +@vendor 0 IETF + +@inherits diameter_gen_base_rfc6733 + +@messages + + CUR ::= < Diameter Header: 328, REQ > + { Origin-Host } + { Origin-Realm } + 1* { Host-IP-Address } + { Vendor-Id } + { Product-Name } + [ Origin-State-Id ] + * [ Supported-Vendor-Id ] + * [ Auth-Application-Id ] + * [ Acct-Application-Id ] + * [ Vendor-Specific-Application-Id ] + [ Firmware-Revision ] + * [ AVP ] + + + CUA ::= < Diameter Header: 328 > + { Origin-Host } + { Origin-Realm } + { Result-Code } + [ Error-Message ] + * [ AVP ] diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index 25207625be..f8d3cf1d6f 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -2,7 +2,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 @@ -17,11 +17,13 @@ # # %CopyrightEnd% -# Runtime dictionary files in ./dict. Modules will be generated from -# these are included in the app file. +# Runtime dictionary files in ./dict. Modules generated from these are +# included in the app file. DICTS = \ base_rfc3588 \ + base_rfc6733 \ base_accounting \ + acct_rfc6733 \ relay # The yecc grammar for the dictionary parser. @@ -49,6 +51,7 @@ RT_MODULES = \ base/diameter_stats \ base/diameter_sup \ base/diameter_sync \ + base/diameter_traffic \ base/diameter_types \ base/diameter_watchdog \ base/diameter_watchdog_sup \ @@ -67,7 +70,6 @@ CT_MODULES = \ base/diameter_info \ compiler/diameter_codegen \ compiler/diameter_exprecs \ - compiler/diameter_nowarn \ compiler/diameter_dict_scanner \ compiler/diameter_dict_util \ compiler/diameter_make diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 3cb13d7043..8b8c2a6694 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -289,7 +289,7 @@ ports() -> Ts = diameter_reg:match({?MODULE, '_', '_'}), [{type(T), N, Pid} || {{?MODULE, T, {_, {_, S}}}, Pid} <- Ts, {ok, N} <- [inet:port(S)]]. - + ports(Ref) -> Ts = diameter_reg:match({?MODULE, '_', {Ref, '_'}}), [{type(T), N, Pid} || {{?MODULE, T, {R, {_, S}}}, Pid} <- Ts, @@ -484,8 +484,8 @@ transition({diameter, {close, Pid}}, #transport{parent = Pid}) -> %% TLS over SCTP is described in RFC 3436 but has limitations as %% described in RFC 6083. The latter describes DTLS over SCTP, which %% addresses these limitations, DTLS itself being described in RFC -%% 4347. TLS is primarily used over TCP, which the current RFC 3588 -%% draft acknowledges by equating TLS with TLS/TCP and DTLS/SCTP. +%% 4347. TLS is primarily used over TCP, which RFC 6733 acknowledges +%% by equating TLS with TLS/TCP and DTLS/SCTP. transition({diameter, {tls, _Ref, _Type, _Bool}}, _) -> stop; @@ -585,8 +585,7 @@ recv({_, #sctp_assoc_change{state = comm_up, socket = Sock} = S) -> Ref = getr(?REF_KEY), - is_reference(Ref) %% started in new code - andalso publish(T, Ref, Id, Sock), + publish(T, Ref, Id, Sock), up(S#transport{assoc_id = Id, streams = {IS, OS}}); diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl index 7ec7b1c5e7..132088b514 100644 --- a/lib/diameter/src/transport/diameter_tcp.erl +++ b/lib/diameter/src/transport/diameter_tcp.erl @@ -52,7 +52,10 @@ -define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1 -define(LISTENER_TIMEOUT, 30000). --define(FRAGMENT_TIMEOUT, 1000). +-define(DEFAULT_FRAGMENT_TIMEOUT, 1000). + +-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)). +-define(IS_TIMEOUT(N), (infinity == N orelse ?IS_UINT32(N))). %% cb_info passed to ssl. -define(TCP_CB(Mod), {Mod, tcp, tcp_closed, tcp_error}). @@ -72,7 +75,6 @@ {parent :: pid(), transport = self() :: pid()}). --type tref() :: reference(). %% timer reference -type length() :: 0..16#FFFFFF. %% message length from Diameter header -type size() :: non_neg_integer(). %% accumulated binary size -type frag() :: {length(), size(), binary(), list(binary())} @@ -83,8 +85,11 @@ {socket :: inet:socket() | ssl:sslsocket(), %% accept/connect socket parent :: pid(), %% of process that started us module :: module(), %% gen_tcp-like module - frag = <<>> :: binary() | {tref(), frag()}, %% message fragment - ssl :: boolean() | [term()]}). %% ssl options + frag = <<>> :: frag(), %% message fragment + ssl :: boolean() | [term()], %% ssl options + timeout :: infinity | 0..16#FFFFFFFF, %% fragment timeout + tref = false :: false | reference(), %% fragment timer reference + flush = false :: boolean()}). %% flush fragment at timeout? %% The usual transport using gen_tcp can be replaced by anything %% sufficiently gen_tcp-like by passing a 'module' option as the first %% (for simplicity) transport option. The transport_module diameter_etcp @@ -161,7 +166,12 @@ i({T, Ref, Mod, Pid, Opts, Addrs}) %% that does nothing but kill us with the parent until call %% returns. {ok, MPid} = diameter_tcp_sup:start_child(#monitor{parent = Pid}), - {SslOpts, Rest} = ssl(Opts), + {SslOpts, Rest0} = ssl(Opts), + {OwnOpts, Rest} = own(Rest0), + Tmo = proplists:get_value(fragment_timer, + OwnOpts, + ?DEFAULT_FRAGMENT_TIMEOUT), + ?IS_TIMEOUT(Tmo) orelse ?ERROR({fragment_timer, Tmo}), Sock = i(T, Ref, Mod, Pid, SslOpts, Rest, Addrs), MPid ! {stop, self()}, %% tell the monitor to die M = if SslOpts -> ssl; true -> Mod end, @@ -170,7 +180,8 @@ i({T, Ref, Mod, Pid, Opts, Addrs}) #transport{parent = Pid, module = M, socket = Sock, - ssl = SslOpts}; + ssl = SslOpts, + timeout = Tmo}; %% Put the reference in the process dictionary since we now use it %% advertise the ssl socket after TLS upgrade. @@ -196,6 +207,10 @@ i({listen, LRef, APid, {Mod, Opts, Addrs}}) -> erlang:monitor(process, APid), start_timer(#listener{socket = LSock}). +own(Opts) -> + {Own, Rest} = proplists:split(Opts, [fragment_timer]), + {lists:append(Own), Rest}. + ssl(Opts) -> {[SslOpts], Rest} = proplists:split(Opts, [ssl_options]), {ssl_opts(SslOpts), Rest}. @@ -368,8 +383,6 @@ handle_info(T, #monitor{} = S) -> %% # code_change/3 %% --------------------------------------------------------------------------- -code_change(_, {transport, _, _, _, _} = S, _) -> - {ok, #transport{} = list_to_tuple(tuple_to_list(S) ++ [false])}; code_change(_, State, _) -> {ok, State}. @@ -452,6 +465,7 @@ t(T,S) -> %% Initial incoming message when we might need to upgrade to TLS: %% don't request another message until we know. + transition({tcp, Sock, Bin}, #transport{socket = Sock, parent = Pid, frag = Head, @@ -459,13 +473,13 @@ transition({tcp, Sock, Bin}, #transport{socket = Sock, ssl = Opts} = S) when is_list(Opts) -> - case recv1(Head, Bin) of + case rcv(Head, Bin) of {Msg, B} when is_binary(Msg) -> diameter_peer:recv(Pid, Msg), S#transport{frag = B}; Frag -> setopts(M, Sock), - S#transport{frag = Frag} + start_fragment_timer(S#transport{frag = Frag}) end; %% Incoming message. @@ -476,7 +490,7 @@ transition({P, Sock, Bin}, #transport{socket = Sock, when P == tcp, not B; P == ssl, B -> setopts(M, Sock), - recv(Bin, S); + start_fragment_timer(recv(Bin, S)); %% Capabilties exchange has decided on whether or not to run over TLS. transition({diameter, {tls, Ref, Type, B}}, #transport{parent = Pid} @@ -487,7 +501,7 @@ transition({diameter, {tls, Ref, Type, B}}, #transport{parent = Pid} = tls_handshake(Type, B, S), Pid ! {diameter, {tls, Ref}}, setopts(M, Sock), - NS#transport{ssl = B}; + start_fragment_timer(NS#transport{ssl = B}); transition({C, Sock}, #transport{socket = Sock, ssl = B}) @@ -520,8 +534,8 @@ transition({diameter, {close, Pid}}, #transport{parent = Pid, stop; %% Timeout for reception of outstanding packets. -transition({timeout, TRef, flush}, S) -> - flush(TRef, S); +transition({timeout, TRef, flush}, #transport{tref = TRef} = S) -> + flush(S#transport{tref = false}); %% Request for the local port number. transition({resolve_port, Pid}, #transport{socket = Sock, @@ -559,9 +573,7 @@ tls_handshake(Type, true, #transport{socket = Sock, = S) -> {ok, SSock} = tls(Type, Sock, [{cb_info, ?TCP_CB(M)} | Opts]), Ref = getr(?REF_KEY), - is_reference(Ref) %% started in new code - andalso - (true = diameter_reg:add_new({?MODULE, Type, {Ref, SSock}})), + true = diameter_reg:add_new({?MODULE, Type, {Ref, SSock}}), S#transport{socket = SSock, module = ssl}; @@ -576,30 +588,25 @@ tls(accept, Sock, Opts) -> %% recv/2 %% -%% Reassemble fragmented messages and extract multple message sent +%% Reassemble fragmented messages and extract multiple message sent %% using Nagle. recv(Bin, #transport{parent = Pid, frag = Head} = S) -> - case recv1(Head, Bin) of + case rcv(Head, Bin) of {Msg, B} when is_binary(Msg) -> diameter_peer:recv(Pid, Msg), recv(B, S#transport{frag = <<>>}); Frag -> - S#transport{frag = Frag} + S#transport{frag = Frag, + flush = false} end. -%% recv1/2 +%% rcv/2 %% No previous fragment. -recv1(<<>>, Bin) -> +rcv(<<>>, Bin) -> rcv(Bin); -recv1({TRef, Head}, Bin) -> - erlang:cancel_timer(TRef), - rcv(Head, Bin). - -%% rcv/2 - %% Not even the first four bytes of the header. rcv(Head, Bin) when is_binary(Head) -> @@ -614,22 +621,22 @@ rcv({Len, N, Head, Acc}, Bin) -> %% Extract a message for which we have all bytes. rcv(Len, N, Head, Acc) when Len =< N -> - rcv1(Len, bin(Head, Acc)); + recv1(Len, bin(Head, Acc)); %% Wait for more packets. rcv(Len, N, Head, Acc) -> - {start_timer(), {Len, N, Head, Acc}}. + {Len, N, Head, Acc}. -%% rcv/2 +%% rcv/1 %% Nothing left. rcv(<<>> = Bin) -> Bin; -%% Well, this isn't good. Chances are things will go south from here -%% but if we're lucky then the bytes we have extend to an intended -%% message boundary and we can recover by simply discarding them, -%% which is the result of receiving them. +%% The Message Length isn't even sufficient for a header. Chances are +%% things will go south from here but if we're lucky then the bytes we +%% have extend to an intended message boundary and we can recover by +%% simply receiving them. Make it so. rcv(<<_:1/binary, Len:24, _/binary>> = Bin) when Len < 20 -> {Bin, <<>>}; @@ -637,23 +644,23 @@ rcv(<<_:1/binary, Len:24, _/binary>> = Bin) %% Enough bytes to extract a message. rcv(<<_:1/binary, Len:24, _/binary>> = Bin) when Len =< size(Bin) -> - rcv1(Len, Bin); + recv1(Len, Bin); %% Or not: wait for more packets. rcv(<<_:1/binary, Len:24, _/binary>> = Head) -> - {start_timer(), {Len, size(Head), Head, []}}; + {Len, size(Head), Head, []}; %% Not even 4 bytes yet. rcv(Head) -> - {start_timer(), Head}. + Head. -%% rcv1/2 +%% recv1/2 -rcv1(Len, Bin) -> +recv1(Len, Bin) -> <<Msg:Len/binary, Rest/binary>> = Bin, {Msg, Rest}. -%% bin/[12] +%% bin/1-2 bin(Head, Acc) -> list_to_binary([Head | lists:reverse(Acc)]). @@ -664,7 +671,7 @@ bin(Bin) when is_binary(Bin) -> Bin. -%% start_timer/0 +%% flush/1 %% An erroneously large message length may leave us with a fragment %% that lingers if the peer doesn't have anything more to send. Start @@ -677,14 +684,30 @@ bin(Bin) %% since all messages with length problems are discarded this should %% also eventually lead to watchdog failover. -start_timer() -> - erlang:start_timer(?FRAGMENT_TIMEOUT, self(), flush). +%% No fragment to flush. +flush(#transport{frag = <<>>} = S) -> + S; -flush(TRef, #transport{parent = Pid, frag = {TRef, Head}} = S) -> - diameter_peer:recv(Pid, bin(Head)), - S#transport{frag = <<>>}; -flush(_, S) -> - S. +%% Messages have been received since last timer expiry. +flush(#transport{flush = false} = S) -> + start_fragment_timer(S#transport{flush = true}); + +%% No messages since last expiry. +flush(#transport{frag = Frag, parent = Pid} = S) -> + diameter_peer:recv(Pid, bin(Frag)), + S#transport{frag = <<>>}. + +%% start_fragment_timer/1 +%% +%% Start a timer only if there's none running and a message to flush. + +start_fragment_timer(#transport{frag = B, tref = TRef} = S) + when B == <<>>; + TRef /= false -> + S; + +start_fragment_timer(#transport{timeout = Tmo} = S) -> + S#transport{tref = erlang:start_timer(Tmo, self(), flush)}. %% accept/2 diff --git a/lib/diameter/test/.gitignore b/lib/diameter/test/.gitignore index df38dfc5e3..4f19542bbe 100644 --- a/lib/diameter/test/.gitignore +++ b/lib/diameter/test/.gitignore @@ -1,3 +1,4 @@ /log /depend.mk +/coverspec diff --git a/lib/diameter/test/Makefile b/lib/diameter/test/Makefile index aa4b7eaeb1..061f0bcbef 100644 --- a/lib/diameter/test/Makefile +++ b/lib/diameter/test/Makefile @@ -56,7 +56,8 @@ DATA_DIRS = $(sort $(dir $(DATA))) ERL_COMPILE_FLAGS += +warn_export_vars \ +warn_unused_vars \ -I ../include \ - -I ../src/gen + -I ../src/gen \ + $(STRICT_FLAGS) # ---------------------------------------------------- # Targets @@ -64,6 +65,9 @@ ERL_COMPILE_FLAGS += +warn_export_vars \ all debug opt: $(TARGET_FILES) +strict: + $(MAKE) opt STRICT_FLAGS=-Werror + # Require success ... run: $(SUITES) @@ -73,7 +77,7 @@ any: opt clean: rm -f $(TARGET_FILES) - rm -f depend.mk + rm -f depend.mk coverspec realclean: clean rm -rf log @@ -114,7 +118,7 @@ help: @echo " Echo some relevant variables." @echo ======================================== -.PHONY: all any run clean debug docs help info opt realclean +.PHONY: all any run clean debug docs help info opt realclean strict # ---------------------------------------------------- # Special Targets @@ -132,10 +136,21 @@ $(SUITES): log opt | awk '{print} / FAILED /{rc=1} END{exit rc}' rc=0 # Shorter in sed but requires a GNU extension (ie. Q). +cover: log opt coverspec + $(ERL) -noinput \ + -pa $(realpath ../ebin) \ + -sname diameter_cover \ + -s diameter_ct cover \ + -s init stop \ + | awk '{print} / FAILED /{rc=1} END{exit rc}' rc=0 + +coverspec: diameter.cover + sed -f [email protected] $< > $@ + log: mkdir $@ -.PHONY: $(SUITES) +.PHONY: $(SUITES) cover # ---------------------------------------------------- # Release Targets diff --git a/lib/diameter/test/coverspec.sed b/lib/diameter/test/coverspec.sed new file mode 100644 index 0000000000..5e81621593 --- /dev/null +++ b/lib/diameter/test/coverspec.sed @@ -0,0 +1,33 @@ +# +# %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% +# + +# +# Morph diameter.cover into a legitimate cover spec. All that's being +# retained is the list of excluded modules. This is used by Makefile +# when running cover locally. +# + +/^{incl_app,/{ + i\ +{level, details}.\ +{incl_dirs, ["../ebin"]}. + d +} + +/^{excl_mods,/s@ .*@@ diff --git a/lib/diameter/test/depend.sed b/lib/diameter/test/depend.sed index 95dca44984..602d1ab497 100644 --- a/lib/diameter/test/depend.sed +++ b/lib/diameter/test/depend.sed @@ -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 @@ -38,4 +38,4 @@ s@^-include("@@ s@".*@@ G -s@^\(.*\)\n\(.*\)@$(EBIN)/\2.$(EMULATOR): \1@ +s@^\(.*\)\n\(.*\)@\2.$(EMULATOR): \1@ diff --git a/lib/diameter/test/diameter.cover b/lib/diameter/test/diameter.cover index 5fde6c7d01..6586733871 100644 --- a/lib/diameter/test/diameter.cover +++ b/lib/diameter/test/diameter.cover @@ -1,6 +1,8 @@ -%% -*- erlang -*- -{exclude, - [ - ] -}. +{incl_app,diameter,details}. +{excl_mods, diameter, + [diameter_dbg, + diameter_info, + diameter_etcp, + diameter_etcp_sup] +}. diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl new file mode 100644 index 0000000000..89c78d8b57 --- /dev/null +++ b/lib/diameter/test/diameter_3xxx_SUITE.erl @@ -0,0 +1,509 @@ +%% +%% %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 application_opt() request_errors. There's some overlap +%% between this suite and the traffic suite but latter exercises more +%% config. +%% + +-module(diameter_3xxx_SUITE). + +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, + end_per_suite/1, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2]). + +%% testcases +-export([start/1, + send_unknown_application/1, + send_unknown_command/1, + send_ok/1, + send_invalid_avp_bits/1, + send_missing_avp/1, + send_ignore_missing_avp/1, + send_double_error/1, + send_3xxx/1, + send_5xxx/1, + stop/1]). + +%% diameter callbacks +-export([peer_up/3, + peer_down/3, + pick_peer/5, + prepare_request/4, + handle_answer/5, + handle_error/5, + handle_request/3]). + +-include("diameter.hrl"). +-include("diameter_gen_base_rfc6733.hrl"). +%% Use the fact that STR/STA is identical in RFC's 3588 and 6733. + +%% =========================================================================== + +-define(util, diameter_util). +-define(testcase(), proplists:get_value(testcase, get(?MODULE))). +-define(group(Config), begin + put(?MODULE, Config), + ?util:name(proplists:get_value(group, Config)) + end). + +-define(L, atom_to_list). +-define(A, list_to_atom). + +-define(CLIENT, "CLIENT"). +-define(SERVER, "SERVER"). +-define(REALM, "erlang.org"). +-define(HOST(Host, Realm), Host ++ [$.|Realm]). + +-define(ERRORS, [answer, answer_3xxx, callback]). +-define(RFCS, [rfc3588, rfc6733]). +-define(DICT(RFC), ?A("diameter_gen_base_" ++ ?L(RFC))). +-define(DICT, ?DICT(rfc6733)). + +-define(COMMON, ?DIAMETER_APP_ID_COMMON). + +%% Config for diameter:start_service/2. +-define(SERVICE(Name, Errors, RFC), + [{'Origin-Host', Name ++ "." ++ ?REALM}, + {'Origin-Realm', ?REALM}, + {'Host-IP-Address', [{127,0,0,1}]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Auth-Application-Id', [?COMMON]}, + {application, [{dictionary, ?DICT(RFC)}, + {module, ?MODULE}, + {answer_errors, callback}, + {request_errors, Errors}]}]). + +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [{group, ?util:name([E,D])} || E <- ?ERRORS, D <- ?RFCS]. + +groups() -> + Tc = tc(), + [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]} + || E <- ?ERRORS, D <- ?RFCS]. + +init_per_suite(Config) -> + ok = diameter:start(), + Config. + +end_per_suite(_Config) -> + ok = diameter:stop(). + +init_per_group(Group, Config) -> + [{group, Group} | Config]. + +end_per_group(_, _) -> + ok. + +init_per_testcase(Name, Config) -> + [{testcase, Name} | Config]. + +end_per_testcase(_, _) -> + ok. + +tc() -> + [send_unknown_application, + send_unknown_command, + send_ok, + send_invalid_avp_bits, + send_missing_avp, + send_ignore_missing_avp, + send_double_error, + send_3xxx, + send_5xxx]. + +%% =========================================================================== + +%% start/1 + +start(Config) -> + Group = proplists:get_value(group, Config), + [Errors, RFC] = ?util:name(Group), + ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group), + Errors, + RFC)), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, + callback, + rfc6733)), + LRef = ?util:listen(?SERVER, tcp), + ?util:connect(?CLIENT, tcp, LRef). + +%% stop/1 + +stop(_Config) -> + ok = diameter:remove_transport(?CLIENT, true), + ok = diameter:remove_transport(?SERVER, true), + ok = diameter:stop_service(?SERVER), + ok = diameter:stop_service(?CLIENT). + +%% send_unknown_application/1 +%% +%% Send an unknown application that a callback (which shouldn't take +%% place) fails on. + +%% diameter answers. +send_unknown_application([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3007, + %% UNSUPPORTED_APPLICATION + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_unknown_application(Config) -> + send_unknown_application(?group(Config)). + +%% send_unknown_command/1 +%% +%% Send a unknown command that a callback discards. + +%% handle_request discards the request. +send_unknown_command([callback, _]) -> + {error, timeout} = call(); + +%% diameter answers. +send_unknown_command([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3001, + %% UNSUPPORTED_COMMAND + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_unknown_command(Config) -> + send_unknown_command(?group(Config)). + +%% send_ok/1 +%% +%% Send a correct STR that a callback answers with 5002. + +%% Callback answers. +send_ok([_,_]) -> + #diameter_base_STA{'Result-Code' = 5002, %% UNKNOWN_SESSION_ID + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_ok(Config) -> + send_ok(?group(Config)). + +%% send_invalid_avp_bits/1 +%% +%% Send a request with an incorrect length on the optional +%% Origin-State-Id that a callback ignores. + +%% Callback answers. +send_invalid_avp_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 + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_invalid_avp_bits(Config) -> + send_invalid_avp_bits(?group(Config)). + +%% send_missing_avp/1 +%% +%% Send a request with a missing AVP that a callback answers. + +%% diameter answers. +send_missing_avp([answer, rfc6733]) -> + #'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +%% Callback answers. +send_missing_avp([_,_]) -> + #diameter_base_STA{'Result-Code' = 5005, %% MISSING_AVP + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +send_missing_avp(Config) -> + send_missing_avp(?group(Config)). + +%% send_ignore_missing_avp/1 +%% +%% Send a request with a missing AVP that a callback ignores. + +%% diameter answers. +send_ignore_missing_avp([answer, rfc6733]) -> + #'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +%% Callback answers, ignores the error +send_ignore_missing_avp([_,_]) -> + #diameter_base_STA{'Result-Code' = 2001, %% SUCCESS + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_ignore_missing_avp(Config) -> + send_ignore_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. + +%% Callback answers with STA. +send_double_error([callback, _]) -> + #diameter_base_STA{'Result-Code' = 5005, %% MISSING_AVP + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +%% diameter answers with answer-message. +send_double_error([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS + 'Failed-AVP' = [_], + 'AVP' = []} + = call(); + +send_double_error(Config) -> + send_double_error(?group(Config)). + +%% send_3xxx/1 +%% +%% Send a request that's answered with a 3xxx result code. + +%% Callback answers. +send_3xxx([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3999, + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_3xxx(Config) -> + send_3xxx(?group(Config)). + +%% send_5xxx/1 +%% +%% Send a request that's answered with a 5xxx result code. + +%% Callback answers but fails since 5xxx isn't allowed in an RFC 3588 +%% answer-message. +send_5xxx([_, rfc3588]) -> + {error, timeout} = call(); + +%% Callback answers. +send_5xxx([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 5999, + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_5xxx(Config) -> + send_5xxx(?group(Config)). + +%% =========================================================================== + +call() -> + Name = ?testcase(), + diameter:call(?CLIENT, + ?DICT, + #diameter_base_STR + {'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?COMMON, + 'Class' = [?L(Name)]}, + [{extra, [Name]}]). + +%% =========================================================================== +%% diameter callbacks + +%% peer_up/3 + +peer_up(_SvcName, _Peer, State) -> + State. + +%% peer_down/3 + +peer_down(_SvcName, _Peer, State) -> + State. + +%% pick_peer/5 + +pick_peer([Peer], _, ?CLIENT, _State, _Name) -> + {ok, Peer}. + +%% prepare_request/4 + +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name) -> + {send, prepare(Pkt, Caps, Name)}. + +prepare(Pkt0, Caps, send_unknown_application) -> + Req = sta(Pkt0, Caps), + #diameter_packet{bin = <<H:8/binary, 0:32, T/binary>>} + = Pkt + = diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}), + + Pkt#diameter_packet{bin = <<H/binary, 23:32, T/binary>>}; + +prepare(Pkt0, Caps, send_unknown_command) -> + Req = sta(Pkt0, Caps), + #diameter_packet{bin = <<H:5/binary, 275:24, T/binary>>} + = Pkt + = diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}), + + Pkt#diameter_packet{bin = <<H/binary, 572:24, T/binary>>}; + +prepare(Pkt, Caps, T) + when T == send_ok; + T == send_3xxx; + 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} + = 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>>}; + +prepare(Pkt0, Caps, send_double_error) -> + dehost(prepare(Pkt0, Caps, send_invalid_avp_bits)); + +prepare(Pkt, Caps, T) + when T == send_missing_avp; + T == send_ignore_missing_avp -> + Req = sta(Pkt, Caps), + dehost(diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req})). + +sta(Pkt, Caps) -> + #diameter_packet{msg = Req} + = Pkt, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, DR}} + = Caps, + Req#diameter_base_STR{'Session-Id' = diameter:session_id(OH), + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Destination-Realm' = DR}. + +%% Strip Origin-Host. +dehost(#diameter_packet{bin = Bin} = Pkt) -> + <<V, Len:24, H:16/binary, T0/binary>> + = Bin, + {SessionId, T1} = split_avp(T0), + {OriginHost, T} = split_avp(T1), + Delta = size(OriginHost), + Pkt#diameter_packet{bin = <<V, (Len - Delta):24, H/binary, + SessionId/binary, + T/binary>>}. + +%% handle_answer/5 + +handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Name) -> + Pkt#diameter_packet.msg. + +%% handle_error/5 + +handle_error(Reason, _Req, ?CLIENT, _Peer, _Name) -> + {error, Reason}. + +split_avp(<<_:5/binary, Len:24, _/binary>> = Bin) -> + L = pad(Len), + <<Avp:L/binary, T/binary>> = Bin, + {Avp, T}. + +pad(N) + when 0 == N rem 4 -> + N; +pad(N) -> + N - (N rem 4) + 4. + +%% handle_request/3 + +handle_request(#diameter_packet{header = #diameter_header{application_id = 0}, + msg = Msg}, + ?SERVER, + {_, Caps}) -> + request(Msg, Caps). + +request(undefined, _) -> %% unknown command + discard; + +request(#diameter_base_STR{'Class' = [Name]} = Req, Caps) -> + request(?A(Name), Req, Caps). + +request(send_ok, Req, Caps) -> + {reply, #diameter_packet{msg = answer(Req, Caps), + errors = [5002]}}; %% UNKNOWN_SESSION_ID + +request(send_3xxx, _Req, _Caps) -> + {answer_message, 3999}; + +request(send_5xxx, _Req, _Caps) -> + {answer_message, 5999}; + +request(send_invalid_avp_bits, Req, Caps) -> + #diameter_base_STR{'Origin-State-Id' = []} + = Req, + %% Default errors field but a non-answer-message and only 3xxx + %% errors detected means diameter sets neither Result-Code nor + %% Failed-AVP. + {reply, #diameter_packet{msg = answer(Req, Caps)}}; + +request(T, Req, Caps) + when T == send_double_error; + T == send_missing_avp -> + {reply, answer(Req, Caps)}; + +request(send_ignore_missing_avp, Req, Caps) -> + {reply, #diameter_packet{msg = answer(Req, Caps), + errors = false}}. %% ignore errors + +answer(Req, Caps) -> + #diameter_base_STR{'Session-Id' = SId} + = Req, + #diameter_caps{origin_host = {OH,_}, + origin_realm = {OR,_}} + = Caps, + #diameter_base_STA{'Session-Id' = SId, + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Result-Code' = 2001}. %% SUCCESS diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 53332af626..209f72adf1 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_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 @@ -48,7 +48,6 @@ diameter_dict_parser, diameter_dict_util, diameter_exprecs, - diameter_nowarn, diameter_make]). -define(HELP_MODULES, [diameter_dbg, diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl index ae128b8203..9e6619ecdd 100644 --- a/lib/diameter/test/diameter_capx_SUITE.erl +++ b/lib/diameter/test/diameter_capx_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 @@ -27,11 +27,14 @@ -export([suite/0, all/0, groups/0, + init_per_group/2, + end_per_group/2, init_per_testcase/2, end_per_testcase/2]). %% testcases -export([start/1, + vendor_id/1, start_services/1, add_listeners/1, s_no_common_application/1, @@ -53,7 +56,6 @@ peer_down/4]). -include("diameter.hrl"). --include("diameter_gen_base_rfc3588.hrl"). %% =========================================================================== @@ -68,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}, @@ -78,8 +80,10 @@ | [{application, [{alias, A}, {dictionary, D}, {module, [?MODULE, A]}]} - || {A,D} <- [{common, ?DIAMETER_DICT_COMMON}, - {accounting, ?DIAMETER_DICT_ACCOUNTING}]]]). + || {A,D} <- [{base3588, diameter_gen_base_rfc3588}, + {acct3588, diameter_gen_base_accounting}, + {base6733, diameter_gen_base_rfc6733}, + {acct6733, diameter_gen_acct_rfc6733}]]]). -define(A, list_to_atom). -define(L, atom_to_list). @@ -88,29 +92,29 @@ -define(caps, #diameter_caps). -define(packet, #diameter_packet). --define(cea, #diameter_base_CEA). --define(answer_message, #'diameter_base_answer-message'). - -define(fail(T), erlang:error({T, process_info(self(), messages)})). -define(TIMEOUT, 10000). +-define(DICTS, [rfc3588, rfc6733]). + %% =========================================================================== suite() -> [{timetrap, {seconds, 60}}]. all() -> [start, + vendor_id, start_services, - add_listeners, - {group, all}, - {group, all, [parallel]}, - remove_listeners, + add_listeners] + ++ [{group, D, P} || D <- ?DICTS, P <- [[], [parallel]]] + ++ [remove_listeners, stop_services, stop]. groups() -> - [{all, [], lists:flatmap(fun tc/1, tc())}]. + Tc = lists:flatmap(fun tc/1, tc()), + [{D, [], Tc} || D <- ?DICTS]. %% Generate a unique hostname for each testcase so that watchdogs %% don't prevent a connection from being brought up immediately. @@ -118,14 +122,22 @@ init_per_testcase(Name, Config) -> Uniq = ["." ++ integer_to_list(N) || N <- tuple_to_list(now())], [{host, lists:flatten([?L(Name) | Uniq])} | Config]. +init_per_group(Name, Config) -> + [{rfc, Name} | Config]. + +end_per_group(_, _) -> + ok. + end_per_testcase(N, _) when N == start; + N == vendor_id; N == start_services; N == add_listeners; N == remove_listeners; N == stop_services; N == stop -> ok; + end_per_testcase(Name, Config) -> CRef = ?util:read_priv(Config, Name), ok = diameter:remove_transport(?CLIENT, CRef). @@ -147,22 +159,45 @@ 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 %% to simplify config, and because we can. add_listeners(Config) -> - Acct = listen(?SERVER, - [{capabilities, [{'Origin-Host', ?HOST("acct-srv")}, - {'Auth-Application-Id', []}]}, - {applications, [accounting]}, - {capabilities_cb, [fun server_capx/3, acct]}]), - Base = listen(?SERVER, - [{capabilities, [{'Origin-Host', ?HOST("base-srv")}]}, - {capabilities_cb, [fun server_capx/3, base]}]), + Acct = [listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST(H)}, + {'Auth-Application-Id', []}]}, + {applications, [A]}, + {capabilities_cb, [fun server_capx/3, acct]}]) + || {A,H} <- [{acct3588, "acct3588-srv"}, + {acct6733, "acct6733-srv"}]], + Base = [listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST(H)}]}, + {applications, A}, + {capabilities_cb, [fun server_capx/3, base]}]) + || {A,H} <- [{[base3588, acct3588], "base3588-srv"}, + {[base6733, acct6733], "base6733-srv"}]], ?util:write_priv(Config, ?MODULE, {Base, Acct}). %% lref/2 reads remove_listeners(_Config) -> @@ -195,8 +230,9 @@ c_no_common_application(Config) -> client_closed(Config, "acct-srv", fun no_common_application/1, 5010). no_common_application(Config) -> + [Common, _Acct] = apps(Config), connect(Config, acct, [{capabilities, [{'Acct-Application-Id', []}]}, - {applications, [common]}]). + {applications, [Common]}]). %% ==================== %% Ask the base server to speak accounting with an unknown security @@ -209,9 +245,10 @@ c_no_common_security(Config) -> client_closed(Config, "base-srv", fun no_common_security/1, 5017). no_common_security(Config) -> + [Common, _Acct] = apps(Config), connect(Config, base, [{capabilities, [{'Acct-Application-Id', []}, {'Inband-Security-Id', [17, 18]}]}, - {applications, [common]}]). + {applications, [Common]}]). %% ==================== %% Have the base server reject a decent CER with the protocol error @@ -221,18 +258,19 @@ s_unknown_peer(Config) -> server_reject(Config, fun base/1, 3010). c_unknown_peer(Config) -> + Dict0 = dict0(Config), true = diameter:subscribe(?CLIENT), - OH = ?HOST("base-srv"), + OH = host(Config, "base-srv"), {CRef, _} = base(Config), - {'CEA', ?caps{}, - ?packet{msg = ?answer_message{'Origin-Host' = OH, - 'Result-Code' = 3010}}} - = client_recv(CRef). + {'CEA', ?caps{}, ?packet{msg = Msg}} = client_recv(CRef), + + ['diameter_base_answer-message' | _] = Dict0:'#get-'(Msg), + [OH, 3010] = Dict0:'#get-'(['Origin-Host', 'Result-Code'], Msg). base(Config) -> - connect(Config, base, []). + connect(Config, base, [{applications, apps(Config)}]). %% ==================== %% Have the base server reject a decent CER with the non-protocol @@ -266,18 +304,23 @@ s_client_reject(Config) -> end. c_client_reject(Config) -> + Dict0 = dict0(Config), true = diameter:subscribe(?CLIENT), - OH = ?HOST("acct-srv"), + OH = host(Config, "acct-srv"), {CRef, _} = client_reject(Config), {'CEA', {capabilities_cb, _, discard}, ?caps{origin_host = {_, OH}}, - ?packet{msg = ?cea{'Result-Code' = 2001}}} - = client_recv(CRef). + ?packet{msg = CEA}} + = client_recv(CRef), + + [diameter_base_CEA | _] = Dict0:'#get-'(CEA), + [2001] = Dict0:'#get-'(['Result-Code'], CEA). client_reject(Config) -> - connect(Config, acct, [{capabilities_cb, fun client_capx/2}]). + connect(Config, acct, [{capabilities_cb, fun client_capx/2}, + {applications, apps(Config)}]). %% =========================================================================== @@ -327,13 +370,21 @@ server_reject(Config, F, RC) -> client_closed(Config, Host, F, RC) -> true = diameter:subscribe(?CLIENT), - OH = ?HOST(Host), + OH = host(Config, Host), {CRef, _} = F(Config), {'CEA', RC, ?caps{origin_host = {_, OH}}, ?packet{}} = client_recv(CRef). +srv(Config, Host) -> + "rfc" ++ N = atom_to_list(proplists:get_value(rfc, Config)), + [H, "srv" = S] = string:tokens(Host, "-"), + H ++ N ++ "-" ++ S. + +host(Config, Name) -> + ?HOST(srv(Config, Name)). + %% client_recv/1 client_recv(CRef) -> @@ -364,6 +415,18 @@ client_capx(_, ?caps{origin_host = {[_,$_|"client_reject." ++ _], _}}) -> %% =========================================================================== +dict0(Config) -> + case proplists:get_value(rfc, Config) of + rfc3588 -> diameter_gen_base_rfc3588; + rfc6733 -> diameter_gen_base_rfc6733 + end. + +apps(Config) -> + case proplists:get_value(rfc, Config) of + rfc3588 -> [base3588, acct3588]; + rfc6733 -> [base6733, acct6733] + end. + host(Config) -> {_, H} = lists:keyfind(host, 1, Config), ?HOST(H). @@ -394,26 +457,32 @@ opts(PortNr, Opts) -> {port, 0}]} | Opts]. +lref(rfc3588, [LRef, _]) -> + LRef; +lref(rfc6733, [_, LRef]) -> + LRef; + lref(Config, T) -> - case ?util:read_priv(Config, ?MODULE) of - {LRef, _} when T == base -> - LRef; - {_, LRef} when T == acct -> - LRef - end. + lref(proplists:get_value(rfc, Config), + case ?util:read_priv(Config, ?MODULE) of + {R, _} when T == base -> + R; + {_, R} when T == acct -> + R + end). %% =========================================================================== %% diameter callbacks peer_up(?SERVER, - {_, ?caps{origin_host = {"acct-srv." ++ _, + {_, ?caps{origin_host = {"acct" ++ _, [_,$_|"client_reject." ++ _]}}}, State, _) -> State. peer_down(?SERVER, - {_, ?caps{origin_host = {"acct-srv." ++ _, + {_, ?caps{origin_host = {"acct" ++ _, [_,$_|"client_reject." ++ _]}}}, State, _) -> diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index fbd38067a8..dc8cbffc83 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.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 @@ -27,7 +27,8 @@ -include("diameter.hrl"). --define(BASE, diameter_gen_base_rfc3588). +-define(RFC3588, diameter_gen_base_rfc3588). +-define(RFC6733, diameter_gen_base_rfc6733). -define(BOOL, [true, false]). -define(A, list_to_atom). @@ -158,7 +159,8 @@ gen(M, messages, {Name, Code, Flags, _, _}) -> Name = case M:msg_name(Code, lists:member('REQ', Flags)) of N when Name /= 'answer-message' -> N; - '' when Name == 'answer-message', M == ?BASE -> + '' when Name == 'answer-message', (M == ?RFC3588 + orelse M == ?RFC6733) -> Name end, [] = arity(M, Name, Rname); diff --git a/lib/diameter/test/diameter_ct.erl b/lib/diameter/test/diameter_ct.erl index ded50bf6c5..1697287a22 100644 --- a/lib/diameter/test/diameter_ct.erl +++ b/lib/diameter/test/diameter_ct.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 @@ -23,16 +23,23 @@ %% Module used to run suites from Makefile. %% --export([run/1]). +-export([run/1, + cover/0]). %% The makefile looks for signs of failure so ignore the ct:run_test/1 %% return value. -run([Suite]) -> +run(Suites) -> + ct_run([{suite, Suites}]). + +cover() -> + ct_run([{spec, "./testspec"}]). + +ct_run(Opts) -> Start = info(), - ct:run_test([{suite, Suite}, - {logdir, "./log"}, - {auto_compile, false}]), + ct:run_test([{logdir, "./log"}, + {auto_compile, false} + | Opts]), info(Start , info()). info() -> diff --git a/lib/diameter/test/diameter_distribution_SUITE.erl b/lib/diameter/test/diameter_distribution_SUITE.erl new file mode 100644 index 0000000000..01d3507b27 --- /dev/null +++ b/lib/diameter/test/diameter_distribution_SUITE.erl @@ -0,0 +1,372 @@ +%% +%% %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, + ping/1, + start/1, + connect/1, + send_local/1, + send_remote/1, + send_timeout/1, + send_failover/1, + stop/1]). + +%% 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(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(_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 7c1c76f22a..18bdcb1f54 100644 --- a/lib/diameter/test/diameter_event_SUITE.erl +++ b/lib/diameter/test/diameter_event_SUITE.erl @@ -121,7 +121,6 @@ down(Config) -> {applications, [?DICT_ACCT]}, {reconnect_timer, 5000}]), start = event(Svc), - {watchdog, Ref, _, {initial, down}, _} = event(Svc), {closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{}}, _} = event(Svc), {reconnect, Ref, _} = event(Svc). @@ -132,7 +131,6 @@ cea_timeout(Config) -> {Svc, Ref} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2}, {reconnect_timer, 2*?SERVER_CAPX_TMO}]), start = event(Svc), - {watchdog, Ref, _, {initial, down}, _} = event(Svc), {closed, Ref, {'CEA', timeout}, _} = event(Svc). stop(_Config) -> diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index bb820a8bf2..dfd3253827 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_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 @@ -103,9 +103,9 @@ -define(SUCCESS, 2001). %% Value of Termination-Cause determines client/server behaviour. --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). --define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_USER_MOVED'). --define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_SESSION_TIMEOUT'). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). +-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). +-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). %% =========================================================================== diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl index 2fde7b9fdb..51ccb1e6ec 100644 --- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl +++ b/lib/diameter/test/diameter_gen_sctp_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 @@ -355,7 +355,7 @@ open(Opts) -> gen_sctp:open([{ip, ?ADDR}, {port, 0}, {active, true}, binary, {recbuf, 1 bsl 16}, {sndbuf, 1 bsl 16} | Opts]). - + %% assoc/1 assoc(Sock) -> diff --git a/lib/diameter/test/diameter_length_SUITE.erl b/lib/diameter/test/diameter_length_SUITE.erl new file mode 100644 index 0000000000..ffb19d2288 --- /dev/null +++ b/lib/diameter/test/diameter_length_SUITE.erl @@ -0,0 +1,289 @@ +%% +%% %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 transport_opt() length_errors. +%% + +-module(diameter_length_SUITE). + +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, + end_per_suite/1, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2]). + +%% testcases +-export([start/1, + send/1, + stop/1]). + +%% diameter callbacks +-export([peer_up/3, + peer_down/3, + pick_peer/5, + prepare_request/4, + handle_answer/5, + handle_error/5, + handle_request/3]). + +-include("diameter.hrl"). +-include("diameter_gen_base_rfc3588.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(CLIENT, "CLIENT"). +-define(SERVER, "SERVER"). +-define(REALM, "erlang.org"). +-define(HOST(Host, Realm), Host ++ [$.|Realm]). +-define(DICT, diameter_gen_base_rfc3588). + +%% Config for diameter:start_service/2. +-define(SERVICE(Name), + [{'Origin-Host', Name ++ "." ++ ?REALM}, + {'Origin-Realm', ?REALM}, + {'Host-IP-Address', [{127,0,0,1}]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {application, [{dictionary, ?DICT}, + {module, ?MODULE}, + {answer_errors, callback}]}]). + +-define(SUCCESS, + ?'DIAMETER_BASE_RESULT-CODE_SUCCESS'). +-define(MISSING_AVP, + ?'DIAMETER_BASE_RESULT-CODE_MISSING_AVP'). +-define(INVALID_MESSAGE_LENGTH, + ?'DIAMETER_BASE_RESULT-CODE_INVALID_MESSAGE_LENGTH'). + +-define(LOGOUT, + ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). + +-define(GROUPS, [exit, handle, discard]). + +-define(L, atom_to_list). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [{group, G} || G <- ?GROUPS]. + +groups() -> + [{G, [], [start, send, stop]} || G <- ?GROUPS]. + +init_per_suite(Config) -> + ok = diameter:start(), + Config. + +end_per_suite(_Config) -> + ok = diameter:stop(). + +init_per_group(Group, Config) -> + [{group, Group} | Config]. + +end_per_group(_, _) -> + ok. + +init_per_testcase(_Name, Config) -> + Config. + +end_per_testcase(_, _) -> + ok. + +origin(exit) -> 0; +origin(handle) -> 1; +origin(discard) -> 2; + +origin(0) -> exit; +origin(1) -> handle; +origin(2) -> discard. + +%% =========================================================================== + +%% start/1 + +start(Config) -> + Group = proplists:get_value(group, Config), + ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group))), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)), + LRef = ?util:listen(?SERVER, + tcp, + [{length_errors, Group}]), + ?util:connect(?CLIENT, + tcp, + LRef, + [{capabilities, [{'Origin-State-Id', origin(Group)}]}]). + +%% stop/1 + +stop(_Config) -> + ok = diameter:remove_transport(?CLIENT, true), + ok = diameter:remove_transport(?SERVER, true), + ok = diameter:stop_service(?SERVER), + ok = diameter:stop_service(?CLIENT). + +%% send/1 + +%% Server transport exits on messages of insuffient length. +send(exit) -> + %% Transport exit is followed by failover but there's only one + %% transport to choose from. + {error, failover} = call(4); + +%% Server transport receives messages of insufficient length. +send(handle) -> + %% Message Length too large: diameter_tcp flushes the request + %% when no additional bytes arrive. + #diameter_base_STA{'Result-Code' = ?INVALID_MESSAGE_LENGTH} + = call(4), + %% Another request answered as it should. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0), + %% Message Length conveniently small: the trailing optional + %% Origin-State-Id isn't included in the received request. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(-12), + %% Server receives Origin-State-Id AVP as the first 12 bytes of + %% the next request: AVP <<Code:32, Flags:8, Len:24, Data:32>> is + %% interpreted as header <<Version:8, Len:24, Flags:8, Code:24, + %% ApplId: 32>>. In particular, the AVP Length 12 = 00001100 is + %% interpreted as Command Flags, so R=0 and the request is + %% interpreted as an unsolicited answer. Increase Message Length + %% to have the server receive all bytes sent thusfar. + {error, timeout} + = call(12), + %% Another request answered as it should. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0), + %% Shorten Message Length so much that that the server doesn't + %% receive the required Termination-Cause AVP. + #diameter_base_STA{'Result-Code' = ?MISSING_AVP} + = call(-24); + +%% Server transport discards message of insufficient length. +send(discard) -> + %% First request times out when the server discards it but a + %% second succeeds since the transport remains up. + {error, timeout} + = call(4), + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0); + +send(Config) -> + send(proplists:get_value(group, Config)). + +%% =========================================================================== + +call(Delta) -> + diameter:call(?CLIENT, + ?DICT, + #diameter_base_STR + {'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?DIAMETER_APP_ID_COMMON, + 'Origin-State-Id' = [7]}, + [{extra, [Delta]}]). + +%% =========================================================================== +%% diameter callbacks + +%% peer_up/3 + +peer_up(_SvcName, _Peer, State) -> + State. + +%% peer_down/3 + +peer_down(_SvcName, _Peer, State) -> + State. + +%% pick_peer/5 + +pick_peer([Peer], _, ?CLIENT, _State, _Delta) -> + {ok, Peer}. + +%% prepare_request/4 + +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Delta) -> + {send, resize(Delta, prepare(Pkt, Caps))}. + +prepare(#diameter_packet{msg = Req0} = Pkt, Caps) -> + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, DR}} + = Caps, + Req = Req0#diameter_base_STR{'Session-Id' = diameter:session_id(OH), + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Destination-Realm' = DR}, + diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req}). + +resize(0, Pkt) -> + Pkt; +resize(Delta, #diameter_packet{bin = Bin} = Pkt) -> + Pkt#diameter_packet{bin = resize(Delta, Bin)}; + +resize(Delta, <<V, Len:24, T/binary>>) -> + <<V, (Len + Delta):24, T/binary>>. + +%% handle_answer/5 + +handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Delta) -> + Pkt#diameter_packet.msg. + +%% handle_error/5 + +handle_error(Reason, _Req, ?CLIENT, _Peer, _Delta) -> + {error, Reason}. + +%% handle_request/3 + +handle_request(Pkt, ?SERVER, {_Ref, Caps}) -> + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}, + origin_state_id = {_,[Id]}} + = Caps, + answer(origin(Id), + Pkt, + #diameter_base_STA{'Result-Code' = ?SUCCESS, + 'Session-Id' = diameter:session_id(OH), + 'Origin-Host' = OH, + 'Origin-Realm' = OR}). + +answer(Group, #diameter_packet{errors = Es}, Ans) -> + answer(Group, Es, Ans); + +%% No errors: just answer. +answer(_, [], Ans) -> + {reply, Ans}; + +%% Otherwise an invalid length should only reach the callback if +%% length_errors = handle. +answer(Group, [RC|_], Ans) + when RC == ?INVALID_MESSAGE_LENGTH, Group == handle; + RC /= ?INVALID_MESSAGE_LENGTH -> + {reply, Ans}. diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl index f10d82bdf8..735a908d97 100644 --- a/lib/diameter/test/diameter_relay_SUITE.erl +++ b/lib/diameter/test/diameter_relay_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 @@ -107,7 +107,7 @@ -define(LOOP_DETECTED, 3005). -define(UNABLE_TO_DELIVER, 3002). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). -define(AUTHORIZE_ONLY, ?'DIAMETER_BASE_RE-AUTH-REQUEST-TYPE_AUTHORIZE_ONLY'). %% =========================================================================== diff --git a/lib/diameter/test/diameter_stats_SUITE.erl b/lib/diameter/test/diameter_stats_SUITE.erl index 8b7d8cb1b6..76ff764671 100644 --- a/lib/diameter/test/diameter_stats_SUITE.erl +++ b/lib/diameter/test/diameter_stats_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 @@ -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) -> @@ -69,7 +71,7 @@ reg(_) -> true = ?stat:reg(Ref), false = ?stat:reg(Ref). %% duplicate -incr(_) -> +incr(_) -> Ref = '_', Ctr = x, false = ?stat:incr(Ctr), %% not registered, @@ -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 6cc34b20c5..77194a0f56 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_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 @@ -122,7 +122,7 @@ {capabilities, Caps}]}). -define(SUCCESS, 2001). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). %% =========================================================================== diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index b03a9ce4d1..781ed234cc 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -80,7 +80,9 @@ send_multiple_filters_2/1, send_multiple_filters_3/1, send_anything/1, + outstanding/1, remove_transports/1, + empty/1, stop_services/1, stop/1]). @@ -97,11 +99,21 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). -include("diameter_gen_base_accounting.hrl"). +%% The listening transports use RFC 3588 dictionaries, the client +%% transports use either 3588 or 6733. (So can't use the record +%% definitions in the latter case.) %% =========================================================================== -define(util, diameter_util). +-define(A, list_to_atom). +-define(L, atom_to_list). + +%% Don't use is_record/2 since dictionary hrl's aren't included. +%% (Since they define conflicting reqcords with the same names.) +-define(is_record(Rec, Name), (Name == element(1, Rec))). + -define(ADDR, {127,0,0,1}). -define(CLIENT, "CLIENT"). @@ -111,9 +123,6 @@ -define(EXTRA, an_extra_argument). --define(BASE, ?DIAMETER_DICT_COMMON). --define(ACCT, ?DIAMETER_DICT_ACCOUNTING). - %% Sequence mask for End-to-End and Hop-by-Hop identifiers. -define(CLIENT_MASK, {1,26}). %% 1 in top 6 bits @@ -123,9 +132,14 @@ %% How to send answers, in a diameter_packet or not. -define(CONTAINERS, [pkt, msg]). -%% Send over multiple connections that are mapped onto -%% [{E,P} || E <- ?ENCODINGS, P <- ?CONTAINERS]. --define(CONNECTIONS, [c0,c1,c2,c3]). +%% Which common dictionary to use in the clients. +-define(RFCS, [rfc3588, rfc6733]). + +-record(group, + {client_encoding, + client_dict0, + server_encoding, + server_container}). %% Not really what we should be setting unless the message is sent in %% the common application but diameter doesn't care. @@ -134,6 +148,17 @@ %% An Application-ID the server doesn't support. -define(BAD_APP, 42). +%% A common match when receiving answers in a client. +-define(answer_message(SessionId, ResultCode), + ['answer-message', + {'Session-Id', SessionId}, + {'Origin-Host', _}, + {'Origin-Realm', _}, + {'Result-Code', ResultCode} + | _]). +-define(answer_message(ResultCode), + ?answer_message(_, ResultCode)). + %% Config for diameter:start_service/2. -define(SERVICE(Name), [{'Origin-Host', Name ++ "." ++ ?REALM}, @@ -147,30 +172,33 @@ | [{application, [{dictionary, D}, {module, ?MODULE}, {answer_errors, callback}]} - || D <- [?BASE, ?ACCT]]]). + || D <- [diameter_gen_base_rfc3588, + diameter_gen_base_accounting, + diameter_gen_base_rfc6733, + diameter_gen_acct_rfc6733]]]). -define(SUCCESS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_SUCCESS'). + ?'DIAMETER_BASE_RESULT-CODE_SUCCESS'). -define(COMMAND_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_COMMAND_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_COMMAND_UNSUPPORTED'). -define(TOO_BUSY, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_TOO_BUSY'). + ?'DIAMETER_BASE_RESULT-CODE_TOO_BUSY'). -define(APPLICATION_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_APPLICATION_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_APPLICATION_UNSUPPORTED'). -define(INVALID_HDR_BITS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_HDR_BITS'). + ?'DIAMETER_BASE_RESULT-CODE_INVALID_HDR_BITS'). -define(INVALID_AVP_BITS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_AVP_BITS'). + ?'DIAMETER_BASE_RESULT-CODE_INVALID_AVP_BITS'). -define(AVP_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_AVP_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_AVP_UNSUPPORTED'). -define(UNSUPPORTED_VERSION, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_UNSUPPORTED_VERSION'). + ?'DIAMETER_BASE_RESULT-CODE_UNSUPPORTED_VERSION'). -define(REALM_NOT_SERVED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_REALM_NOT_SERVED'). + ?'DIAMETER_BASE_RESULT-CODE_REALM_NOT_SERVED'). -define(UNABLE_TO_DELIVER, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_UNABLE_TO_DELIVER'). + ?'DIAMETER_BASE_RESULT-CODE_UNABLE_TO_DELIVER'). -define(INVALID_AVP_LENGTH, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_AVP_LENGTH'). + ?'DIAMETER_BASE_RESULT-CODE_INVALID_AVP_LENGTH'). -define(EVENT_RECORD, ?'DIAMETER_BASE_ACCOUNTING-RECORD-TYPE_EVENT_RECORD'). @@ -180,16 +208,11 @@ ?'DIAMETER_BASE_RE-AUTH-REQUEST-TYPE_AUTHORIZE_AUTHENTICATE'). -define(LOGOUT, - ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). + ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). -define(BAD_ANSWER, - ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_BAD_ANSWER'). + ?'DIAMETER_BASE_TERMINATION-CAUSE_BAD_ANSWER'). -define(USER_MOVED, - ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_USER_MOVED'). - --define(A, list_to_atom). --define(L, atom_to_list). - --define(NAME(A,B), ?A(?L(A) ++ "," ++ ?L(B))). + ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). %% =========================================================================== @@ -198,20 +221,27 @@ suite() -> all() -> [start, start_services, add_transports, result_codes] - ++ [{group, ?util:name([R,C,A]), P} || R <- ?ENCODINGS, - C <- ?CONTAINERS, - A <- ?ENCODINGS, - P <- [[], [parallel]]] - ++ [remove_transports, stop_services, stop]. + ++ [{group, ?util:name([R,D,A,C]), P} || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS, + P <- [[], [parallel]]] + ++ [outstanding, remove_transports, empty, stop_services, stop]. groups() -> Ts = tc(), - [{?util:name([R,C,A]), [], Ts} || R <- ?ENCODINGS, - C <- ?CONTAINERS, - A <- ?ENCODINGS]. + [{?util:name([R,D,A,C]), [], Ts} || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS]. init_per_group(Name, Config) -> - [{group, Name} | Config]. + [R,D,A,C] = ?util:name(Name), + G = #group{client_encoding = R, + client_dict0 = dict0(D), + server_encoding = A, + server_container = C}, + [{group, G} | Config]. end_per_group(_, _) -> ok. @@ -282,12 +312,33 @@ start_services(_Config) -> | ?SERVICE(?CLIENT)]). add_transports(Config) -> - LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx/2}]), - Cs = [?util:connect(?CLIENT, tcp, LRef, [{id, C}, - {capabilities, [osi(C)]}]) - || C <- ?CONNECTIONS], + LRef = ?util:listen(?SERVER, + tcp, + [{capabilities_cb, fun capx/2}, + {applications, apps(rfc3588)}]), + Cs = [?util:connect(?CLIENT, + tcp, + LRef, + [{id, Id}, + {capabilities, [{'Origin-State-Id', origin(Id)}]}, + {applications, apps(D)}]) + || A <- ?ENCODINGS, + C <- ?CONTAINERS, + D <- ?RFCS, + Id <- [{A,C}]], + %% The server uses the client's Origin-State-Id to decide how to + %% answer. ?util:write_priv(Config, "transport", [LRef | Cs]). +apps(D0) -> + D = dict0(D0), + [acct(D), D]. + +%% Ensure there are no outstanding requests in request table. +outstanding(_Config) -> + [] = [T || T <- ets:tab2list(diameter_request), + is_atom(element(1,T))]. + remove_transports(Config) -> [LRef | Cs] = ?util:read_priv(Config, "transport"), [?util:disconnect(?CLIENT, C, ?SERVER, LRef) || C <- Cs]. @@ -296,6 +347,10 @@ stop_services(_Config) -> ok = diameter:stop_service(?CLIENT), ok = diameter:stop_service(?SERVER). +%% Ensure even transports have been removed from request table. +empty(_Config) -> + [] = ets:tab2list(diameter_request). + stop(_Config) -> ok = diameter:stop(). @@ -303,10 +358,6 @@ capx(_, #diameter_caps{origin_host = {OH,DH}}) -> io:format("connection: ~p -> ~p~n", [DH,OH]), ok. -osi(Id) -> - [$c,N] = atom_to_list(Id), - {'Origin-State-Id', N - $0}. - %% =========================================================================== %% Ensure that result codes have the expected values. @@ -329,7 +380,7 @@ send_ok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 1}], - #diameter_base_accounting_ACA{'Result-Code' = ?SUCCESS} + ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send an accounting ACR that the server answers badly to. @@ -337,7 +388,7 @@ send_nok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 0}], - #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} + ?answer_message(?INVALID_AVP_BITS) = call(Config, Req). %% Send an ACR and expect success. @@ -345,7 +396,7 @@ send_eval(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 3}], - #diameter_base_accounting_ACA{'Result-Code' = ?SUCCESS} + ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send an accounting ACR that the server tries to answer with an @@ -362,46 +413,44 @@ send_protocol_error(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 4}], - #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY} + ?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_arbitrary(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{name = 'Class', value = "XXX"}]}], - #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'AVP' = Avps} + ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), - [#diameter_avp{name = 'Class', - value = "XXX"}] - = Avps. + {'AVP', [#diameter_avp{name = 'Class', + value = "XXX"}]} + = lists:last(Avps). %% Send an unknown AVP (to some client) and check that it comes back. send_unknown(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, is_mandatory = false, data = <<17>>}]}], - #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'AVP' = Avps} + ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), - [#diameter_avp{code = 999, - is_mandatory = false, - data = <<17>>}] - = Avps. + {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, + data = <<17>>}]} + = lists:last(Avps). %% Ditto but set the M flag. send_unknown_mandatory(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, is_mandatory = true, data = <<17>>}]}], - #diameter_base_ASA{'Result-Code' = ?AVP_UNSUPPORTED, - 'Failed-AVP' = Failed} + ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] = call(Config, Req), - [#'diameter_base_Failed-AVP'{'AVP' = Avps}] = Failed, + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), [#diameter_avp{code = 999, is_mandatory = true, data = <<17>>}] - = Avps. + = As. %% Send an STR that the server ignores. send_noreply(Config) -> @@ -411,32 +460,32 @@ send_noreply(Config) -> %% Send an unsupported command and expect 3001. send_unsupported(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?COMMAND_UNSUPPORTED} + ?answer_message(?COMMAND_UNSUPPORTED) = call(Config, Req). %% Send an unsupported application and expect 3007. send_unsupported_app(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?APPLICATION_UNSUPPORTED} + ?answer_message(?APPLICATION_UNSUPPORTED) = call(Config, Req). %% Send a request with the E bit set and expect 3008. send_error_bit(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?INVALID_HDR_BITS} + ?answer_message(?INVALID_HDR_BITS) = call(Config, Req). %% Send a bad version and check that we get 5011. send_unsupported_version(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - #diameter_base_STA{'Result-Code' = ?UNSUPPORTED_VERSION} + ['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}], - #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} + ?answer_message(?INVALID_AVP_BITS) = call(Config, Req). %% Send a request containing an AVP length that doesn't match the @@ -444,7 +493,7 @@ send_invalid_avp_bits(Config) -> send_invalid_avp_length(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - #'diameter_base_STA'{'Result-Code' = ?INVALID_AVP_LENGTH} + ['STA', _SessionId, {'Result-Code', ?INVALID_AVP_LENGTH} | _] = call(Config, Req). %% Send a request containing 5xxx errors that the server rejects with @@ -452,14 +501,14 @@ send_invalid_avp_length(Config) -> send_invalid_reject(Config) -> Req = ['STR', {'Termination-Cause', ?USER_MOVED}], - #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY} + ?answer_message(?TOO_BUSY) = call(Config, Req). %% Send something long that will be fragmented by TCP. send_long(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'User-Name', [lists:duplicate(1 bsl 20, $X)]}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send something for which pick_peer finds no suitable peer. @@ -484,14 +533,14 @@ send_any_1(Config) -> send_any_2(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], - #'diameter_base_answer-message'{'Result-Code' = ?UNABLE_TO_DELIVER} + ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req, [{filter, {any, [host, realm]}}]). %% Send with a conjunctive filter. send_all_1(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM), - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [{host, any}, {realm, Realm}]}}]). send_all_2(Config) -> @@ -509,8 +558,7 @@ send_timeout(Config) -> %% received the Session-Id. send_error(Config) -> Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}], - #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY, - 'Session-Id' = SId} + ?answer_message(SId, ?TOO_BUSY) = call(Config, Req), undefined /= SId. @@ -520,10 +568,9 @@ send_detach(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], Ref = make_ref(), ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]), - #diameter_packet{msg = Rec, errors = []} - = receive {Ref, T} -> T after 2000 -> false end, - #diameter_base_STA{'Result-Code' = ?SUCCESS} - = Rec. + Ans = receive {Ref, T} -> T after 2000 -> false end, + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] + = Ans. %% Send a request which can't be encoded and expect {error, encode}. send_encode_error(Config) -> @@ -533,11 +580,11 @@ send_encode_error(Config) -> send_destination_1(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, ?REALM)]}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [host, realm]}}]). send_destination_2(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [host, realm]}}]). %% Send with filtering on and expect failure when specifying an @@ -558,12 +605,12 @@ send_destination_4(Config) -> send_destination_5(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Realm', "unknown.org"}], - #'diameter_base_answer-message'{'Result-Code' = ?REALM_NOT_SERVED} + ?answer_message(?REALM_NOT_SERVED) = call(Config, Req). send_destination_6(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], - #'diameter_base_answer-message'{'Result-Code' = ?UNABLE_TO_DELIVER} + ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req). %% Specify an invalid option and expect failure. @@ -597,7 +644,7 @@ send_bad_filter(Config, F) -> %% Specify multiple filter options and expect them be conjunctive. send_multiple_filters_1(Config) -> Fun = fun(#diameter_caps{}) -> true end, - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = send_multiple_filters(Config, [host, {eval, Fun}]). send_multiple_filters_2(Config) -> E = {erlang, is_tuple, []}, @@ -608,7 +655,7 @@ send_multiple_filters_3(Config) -> E2 = {erlang, is_tuple, []}, E3 = {erlang, is_record, [diameter_caps]}, E4 = [{erlang, is_record, []}, diameter_caps], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]). send_multiple_filters(Config, Fs) -> @@ -619,7 +666,7 @@ send_multiple_filters(Config, Fs) -> %% only the return value from the prepare_request callback being %% significant. send_anything(Config) -> - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, anything). %% =========================================================================== @@ -629,56 +676,69 @@ call(Config, Req) -> call(Config, Req, Opts) -> Name = proplists:get_value(testcase, Config), - [Encoding, C, E] = ?util:name(proplists:get_value(group, Config)), + #group{client_encoding = ReqEncoding, + client_dict0 = Dict0} + = Group + = proplists:get_value(group, Config), diameter:call(?CLIENT, - dict(Req), - msg(Req, Encoding), - [{extra, [Name, client(E,C)]} | Opts]). + dict(Req, Dict0), + msg(Req, ReqEncoding, Dict0), + [{extra, [Name, Group]} | Opts]). -client(E, C) -> - list_to_atom([$c, $0 + 2*codec(E) + container(C)]). +origin({A,C}) -> + 2*codec(A) + container(C); -client(N) -> - {codec(N bsr 1), container(N rem 2)}. +origin(N) -> + {codec(N band 2), container(N rem 2)}. + +%% Map booleans, but the readable atoms are part of (constructed) +%% group names, so it's good that they're readable. codec(record) -> 0; -codec(list) -> 1; +codec(list) -> 1; codec(0) -> record; -codec(1) -> list. - -%% Here we're just mapping booleans but the readable atoms are part of -%% (constructed) group names, so it's good that they're readable. +codec(_) -> list. container(pkt) -> 0; container(msg) -> 1; container(0) -> pkt; -container(1) -> msg. +container(_) -> msg. -msg([H|T], record) +msg([H|_] = Msg, record = E, diameter_gen_base_rfc3588) when H == 'ACR'; H == 'ACA' -> - ?ACCT:'#new-'(?ACCT:msg2rec(H), T); -msg([H|T], record) -> - ?BASE:'#new-'(?BASE:msg2rec(H), T); -msg(T, _) -> - T. - -dict(['ACR' | _]) -> - ?ACCT; -dict(#diameter_base_accounting_ACR{}) -> - ?ACCT; -dict(_) -> - ?BASE. + msg(Msg, E, diameter_gen_base_accounting); +msg([H|_] = Msg, record = E, diameter_gen_base_rfc6733) + when H == 'ACR'; + H == 'ACA' -> + msg(Msg, E, diameter_gen_acct_rfc6733); +msg([H|T], record, Dict) -> + Dict:'#new-'(Dict:msg2rec(H), T); +msg(Msg, _, _) -> + Msg. + +dict0(D) -> + ?A("diameter_gen_base_" ++ ?L(D)). + +dict(Msg, Dict0) + when 'ACR' == hd(Msg); + 'ACA' == hd(Msg); + ?is_record(Msg, diameter_base_accounting_ACR); + ?is_record(Msg, diameter_base_accounting_ACA) -> + acct(Dict0); +dict(_, Dict0) -> + Dict0. + +acct(diameter_gen_base_rfc3588) -> + diameter_gen_base_accounting; +acct(diameter_gen_base_rfc6733) -> + diameter_gen_acct_rfc6733. %% Set only values that aren't already. -set([H|T], Vs) -> +set(_, [H|T], Vs) -> [H | Vs ++ T]; -set(#diameter_base_accounting_ACR{} = Rec, Vs) -> - set(Rec, Vs, ?ACCT); -set(Rec, Vs) -> - set(Rec, Vs, ?BASE). - -set(Rec, Vs, Dict) -> +set(#group{client_dict0 = Dict0} = _Group, Rec, Vs) -> + Dict = dict(Rec, Dict0), lists:foldl(fun({F,_} = FV, A) -> set(Dict, Dict:'#get-'(F, A), FV, A) end, @@ -707,17 +767,18 @@ peer_down(_SvcName, _Peer, State) -> %% pick_peer/6-7 -pick_peer(Peers, _, ?CLIENT, _State, Name, Id) +pick_peer(Peers, _, ?CLIENT, _State, Name, Group) when Name /= send_detach -> - find(Id, Peers). + find(Group, Peers). pick_peer(_Peers, _, ?CLIENT, _State, send_nopeer, _, ?EXTRA) -> false; -pick_peer(Peers, _, ?CLIENT, _State, send_detach, Id, {_,_}) -> - find(Id, Peers). +pick_peer(Peers, _, ?CLIENT, _State, send_detach, Group, {_,_}) -> + find(Group, Peers). -find(Id, Peers) -> +find(#group{server_encoding = A, server_container = C}, Peers) -> + Id = {A,C}, [P] = [P || P <- Peers, id(Id, P)], {ok, P}. @@ -731,39 +792,41 @@ id(Id, {Pid, _Caps}) -> prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, send_discard, _) -> {discard, unprepared}; -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, _) -> - {send, prepare(Pkt, Caps, Name)}. +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, Group) -> + {send, prepare(Pkt, Caps, Name, Group)}. -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, _, _) -> - {eval_packet, {send, prepare(Pkt, Caps)}, [fun log/2, detach]}. +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, Group, _) -> + {eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}. -log(#diameter_packet{} = P, T) -> +log(#diameter_packet{bin = Bin} = P, T) + when is_binary(Bin) -> io:format("~p: ~p~n", [T,P]). -%% prepare/3 +%% prepare/4 -prepare(Pkt, Caps, send_invalid_avp_bits) -> - Req = prepare(Pkt, Caps), +prepare(Pkt, Caps, send_invalid_avp_bits, #group{client_dict0 = Dict0} + = Group) -> + Req = prepare(Pkt, Caps, Group), %% Last AVP in our STR is Termination-Cause of type Unsigned32: %% set its length improperly. #diameter_packet{header = #diameter_header{length = L}, bin = B} = E - = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + = 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>>}; -prepare(Pkt, Caps, N) +prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) when N == send_invalid_avp_length; N == send_invalid_reject -> - Req = prepare(Pkt, Caps), + Req = prepare(Pkt, Caps, Group), %% Second last AVP in our STR is Auth-Application-Id of type %% Unsigned32: Send a value of length 8. #diameter_packet{header = #diameter_header{length = L}, bin = B0} = E - = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + = 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 @@ -774,107 +837,111 @@ prepare(Pkt, Caps, N) 0:32/integer, T/binary>>}; -prepare(Pkt, Caps, send_unsupported) -> - Req = prepare(Pkt, Caps), +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(?BASE, Pkt#diameter_packet{msg = Req}), + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), E#diameter_packet{bin = <<H/binary, 42:24/integer, T/binary>>}; -prepare(Pkt, Caps, send_unsupported_app) -> - Req = prepare(Pkt, Caps), +prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0} + = Group) -> + Req = prepare(Pkt, Caps, Group), #diameter_packet{bin = <<H:8/binary, _ApplId:4/binary, T/binary>>} = E - = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), E#diameter_packet{bin = <<H/binary, ?BAD_APP:32/integer, T/binary>>}; -prepare(Pkt, Caps, send_error_bit) -> +prepare(Pkt, Caps, send_error_bit, Group) -> #diameter_packet{header = Hdr} = Pkt, Pkt#diameter_packet{header = Hdr#diameter_header{is_error = true}, - msg = prepare(Pkt, Caps)}; + msg = prepare(Pkt, Caps, Group)}; -prepare(Pkt, Caps, send_unsupported_version) -> +prepare(Pkt, Caps, send_unsupported_version, Group) -> #diameter_packet{header = Hdr} = Pkt, Pkt#diameter_packet{header = Hdr#diameter_header{version = 42}, - msg = prepare(Pkt, Caps)}; + msg = prepare(Pkt, Caps, Group)}; -prepare(Pkt, Caps, send_anything) -> +prepare(Pkt, Caps, send_anything, Group) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - prepare(Pkt#diameter_packet{msg = Req}, Caps); + prepare(Pkt#diameter_packet{msg = Req}, Caps, Group); -prepare(Pkt, Caps, _Name) -> - prepare(Pkt, Caps). +prepare(Pkt, Caps, _Name, Group) -> + prepare(Pkt, Caps, Group). -%% prepare/2 +%% prepare/3 -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_accounting_ACR); +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_accounting_ACR); 'ACR' == hd(Req) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Realm', DR}]); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Realm', DR}]); -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_ASR); +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_ASR); 'ASR' == hd(Req) -> #diameter_caps{origin_host = {OH, DH}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Host', DH}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]); - -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_STR); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Host', DH}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]); + +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_STR); 'STR' == hd(Req) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]); - -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_RAR); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]); + +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_RAR); 'RAR' == hd(Req) -> #diameter_caps{origin_host = {OH, DH}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Host', DH}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]). + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Host', DH}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]). %% prepare_retransmit/5 -prepare_retransmit(_Pkt, false, _Peer, _Name, _Id) -> +prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) -> discard. %% handle_answer/6-7 -handle_answer(Pkt, Req, ?CLIENT, Peer, Name, _Id) -> - answer(Pkt, Req, Peer, Name). +handle_answer(Pkt, Req, ?CLIENT, Peer, Name, Group) -> + answer(Pkt, Req, Peer, Name, Group). -handle_answer(Pkt, _Req, ?CLIENT, _Peer, send_detach, _Id, {Pid, Ref}) -> - Pid ! {Ref, Pkt}. +handle_answer(Pkt, Req, ?CLIENT, Peer, send_detach = Name, Group, X) -> + {Pid, Ref} = X, + Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}. -answer(Pkt, Req, _Peer, Name) -> - #diameter_packet{header = H, msg = Rec, errors = Es} = Pkt, - ApplId = app(Req, Name), +answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) -> + #diameter_packet{header = H, msg = Ans, errors = Es} = Pkt, + ApplId = app(Req, Name, Dict0), #diameter_header{application_id = ApplId} = H, %% assert - answer(Rec, Es, Name). + Dict = dict(Ans, Dict0), + [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)), + [Dict:rec2msg(R) | Vs]. answer(Rec, [_|_], N) when N == send_invalid_avp_bits; @@ -884,15 +951,15 @@ answer(Rec, [_|_], N) answer(Rec, [], _) -> Rec. -app(_, send_unsupported_app) -> +app(_, send_unsupported_app, _) -> ?BAD_APP; -app(Req, _) -> - Dict = dict(Req), +app(Req, _, Dict0) -> + Dict = dict(Req, Dict0), Dict:id(). %% handle_error/6 -handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Id) -> +handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Group) -> {error, Reason}. %% handle_request/3 @@ -907,14 +974,15 @@ handle_request(#diameter_packet{header = H, msg = M}, ?SERVER, {_Ref, Caps}) -> {V,B} = ?CLIENT_MASK, V = EI bsr B, %% assert V = HI bsr B, %% - #diameter_caps{origin_state_id = {_,[N]}} = Caps, - answer(client(N), request(M, Caps)). + #diameter_caps{origin_state_id = {_,[Id]}} = Caps, + answer(origin(Id), request(M, Caps)). answer(T, {Tag, Action, Post}) -> {Tag, answer(T, Action), Post}; -answer({E,C}, {reply, Ans}) -> - answer(C, {reply, msg(Ans, E)}); -answer(pkt, {reply, Ans}) -> +answer({A,C}, {reply, Ans}) -> + answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)}); +answer(pkt, {reply, Ans}) + when not is_record(Ans, diameter_packet) -> {reply, #diameter_packet{msg = Ans}}; answer(_, T) -> T. @@ -987,7 +1055,8 @@ request(#diameter_base_ASR{'Session-Id' = SId, {'AVP', Avps}]}; %% send_invalid_reject -request(#diameter_base_STR{'Termination-Cause' = ?USER_MOVED}, _Caps) -> +request(#diameter_base_STR{'Termination-Cause' = ?USER_MOVED}, + _Caps) -> {protocol_error, ?TOO_BUSY}; %% send_noreply @@ -997,13 +1066,13 @@ request(#diameter_base_STR{'Termination-Cause' = T}, discard; %% send_destination_5 -request(#diameter_base_STR{'Destination-Realm'= R}, +request(#diameter_base_STR{'Destination-Realm' = R}, #diameter_caps{origin_realm = {OR, _}}) when R /= undefined, R /= OR -> {protocol_error, ?REALM_NOT_SERVED}; %% send_destination_6 -request(#diameter_base_STR{'Destination-Host'= [H]}, +request(#diameter_base_STR{'Destination-Host' = [H]}, #diameter_caps{origin_host = {OH, _}}) when H /= OH -> {protocol_error, ?UNABLE_TO_DELIVER}; diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 5af4ad9ba5..a9872f32e1 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -258,6 +258,9 @@ path(Config, Name) -> lport(M, Ref) -> lport(M, Ref, 1). +lport(M, {Node, Ref}, Tries) -> + rpc:call(Node, ?MODULE, lport, [M, Ref, Tries]); + lport(M, Ref, Tries) -> lp(tmod(M), Ref, Tries). diff --git a/lib/diameter/test/diameter_watchdog_SUITE.erl b/lib/diameter/test/diameter_watchdog_SUITE.erl index 7ce09e93ca..704bf110c7 100644 --- a/lib/diameter/test/diameter_watchdog_SUITE.erl +++ b/lib/diameter/test/diameter_watchdog_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 @@ -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(), @@ -127,91 +134,48 @@ end_per_suite(_Config) -> %% =========================================================================== %% Test the watchdog state machine for the required failover, failback -%% and reopen behaviour. Do this by having the testcase replace -%% diameter_service and start watchdogs, and having this module -%% implement a transport process that plays the role of the peer -%% Diameter node. +%% and reopen behaviour by examining watchdog events. -%reopen(_) -> -% reopen(connect, ?WD(10000), 1, 'DWR'); +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(), +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]]). - %% 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)}}, +%% reopen/6 - {{LH,LW},{CH,CW}} = case Type of - listen -> {Node, Peer}; - connect -> {Peer, Node} - 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). - 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), - - CO = [{transport_module, diameter_tcp}, - {transport_config, CH ++ [{ip, ?ADDR}, {port, 0}, - {raddr, ?ADDR}, {rport, LP}]}, - {watchdog_timer, CW}], - - %% 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), - - [] = 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 @@ -221,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() @@ -234,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() @@ -248,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 @@ -269,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 %% @@ -287,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() @@ -311,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. -recv_reopen(connect, Ref, WdL, WdH) -> - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, WdL, WdH), - ?EVENT({reconnect, Ref, _}, 0); +log_event(E) -> + T = element(1,E), + T == watchdog orelse ?LOG("~p", [T]), + E. -recv_reopen(listen, Ref, _, _) -> - ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, ?PEER_WD). +%% recv_reopen/2 -%% reg/4 +recv_reopen(connect, Ref) -> + {down, reopen} = ?WD_EVENT(Ref), + ?EVENT({reconnect, Ref, _}); + +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,12 +374,160 @@ tpid(listen, Ref, [[{ref, Ref}, {port, [{owner, TPid} | _]} | _]] = lists:filter(fun([{watchdog, {_,_,S}} | _]) -> - S == okay orelse S == reopen + S == okay orelse S == reopen end, As), 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, 20), + [{raddr, ?ADDR}, {rport, LP}]. + +%% =========================================================================== listen(PortNr, Opts) -> gen_tcp:listen(PortNr, Opts). @@ -402,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); @@ -432,7 +580,7 @@ send({SvcName, {_,_,_} = T}, Sock, Bin) -> putr(origin, [OH, OR]), putr(config, T), send(Sock, Bin); - + %% Discard DWA, failback after another timeout in the peer. send({Wd, 0 = No, Msg}, Sock, Bin) -> Origin = getr(origin), @@ -511,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 80b1769d04..beff588a02 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -2,7 +2,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 @@ -22,26 +22,29 @@ COVER_SPEC_FILE = diameter.cover MODULES = \ diameter_ct \ - diameter_util \ diameter_enum \ - diameter_compiler_SUITE \ + diameter_util \ + diameter_3xxx_SUITE \ + diameter_app_SUITE \ + diameter_capx_SUITE \ diameter_codec_SUITE \ diameter_codec_test \ - diameter_app_SUITE \ + diameter_compiler_SUITE \ diameter_dict_SUITE \ - diameter_reg_SUITE \ - diameter_sync_SUITE \ - diameter_stats_SUITE \ - diameter_watchdog_SUITE \ + diameter_distribution_SUITE \ + diameter_dpr_SUITE \ + diameter_event_SUITE \ + diameter_failover_SUITE \ diameter_gen_sctp_SUITE \ - diameter_transport_SUITE \ - diameter_capx_SUITE \ - diameter_traffic_SUITE \ + diameter_length_SUITE \ + diameter_reg_SUITE \ diameter_relay_SUITE \ + diameter_stats_SUITE \ + diameter_sync_SUITE \ diameter_tls_SUITE \ - diameter_failover_SUITE \ - diameter_dpr_SUITE \ - diameter_event_SUITE + diameter_traffic_SUITE \ + diameter_transport_SUITE \ + diameter_watchdog_SUITE HRL_FILES = \ diameter_ct.hrl diff --git a/lib/diameter/test/testspec b/lib/diameter/test/testspec new file mode 100644 index 0000000000..2fd8307281 --- /dev/null +++ b/lib/diameter/test/testspec @@ -0,0 +1,3 @@ + +{suites, ".", all}. +{cover, "./coverspec"}. diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 74f4c57b70..757f29a32e 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,5 +18,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.4 +DIAMETER_VSN = 1.4.1.1 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) |