diameter(3)
๐ diameter(3) โ Erlang Module Definition ๐
okay bestie, welcome to the diameter module! It's giving main-character energy for Diameter protocol stuff, and we're gonna walk through every single detail together. ๐ซถ No skips, no cap. โจ
๐ NAME ๐
diameter โ Main API of the diameter application. periodt. ๐
โจ DESCRIPTION โจ
This module? It's literally the interface you use to implement a Diameter node that sends and receives messages using the Diameter protocol as defined in RFC 6733. ๐
Basic usage is a whole vibe: you create a representation of a locally implemented Diameter node and its capabilities with start_service/2, then add transport capability using add_transport/2, and then send Diameter requests and receive Diameter answers with call/4. Incoming Diameter requests are communicated as callbacks to a diameter_app callback modules as specified in the service configuration. ๐
โ ๏ธ Bestie, beware the difference betweendiameter(not capitalized) and Diameter (capitalized). The former refers to the Erlang application nameddiameterwhose main api is defined here. The latter? That's the Diameter protocol in the sense of RFC 6733. Don't mix them up, iykyk. ๐
Also, the diameter application must be started before calling most functions in this module. No skipping that step, fr fr. ๐
๐งฌ DATA TYPES ๐งฌ
Here's the roster of types, bestie:
Address()DiameterIdentity()Grouped()OctetString()Time()Unsigned32()
Then we got:
UTF8String()โ Types corresponding to RFC 6733 AVP Data Formats. Defined indiameter_dict(4). ๐elapsed_time()โ Elapsed time since a given time. โณapplication_alias() = term()โ Name identifying a Diameter application in service configuration. Passed tocall/4when sending requests defined by the application. ๐ท๏ธapplication_module() = Mod | [Mod | ExtraArgs] | #diameter_callback{}Mod = atom()ExtraArgs = list()
Module implementing the callback interface defined in diameter_app, along with any extra arguments to be appended to those documented. Note that extra arguments specific to an outgoing request can be specified to call/4, in which case those are appended to any module-specific extra arguments.
Specifying a #diameter_callback{} record allows individual functions to be configured in place of the usual diameter_app callbacks. See diameter_callback.erl for details. ๐ง
application_opt()โ Options defining a Diameter application. Has one of the following types. ๐{alias, application_alias()}โ Unique identifier for the application in the scope of the service. Defaults to the value of thedictionaryoption. ๐ท๏ธ{dictionary, atom()}โ Name of an encode/decode module for the Diameter messages defined by the application. These modules are generated from files whose format is documented indiameter_dict(4). ๐{module, application_module()}โ Callback module in which messages of the Diameter application are handled. Seediameter_appfor the required interface and semantics. ๐{state, term()}โ Initial callback state. The prevailing state is passed to somediameter_appcallbacks, which can then return a new state. Defaults to the value of thealiasoption. ๐ง{call_mutates_state, true|false}โ Whether or not thepick_peer/4application callback can modify the application state. Defaults tofalse.
๐จ WARNING:pick_peer/4callbacks are serialized when this option istrue, which is a potential performance bottleneck. A simple Diameter client may suffer no ill effects from using mutable state but a server or agent that responds to incoming request should probably avoid it. ๐ญ
{answer_errors, callback|report|discard}โ Manner in which incoming answer messages containing decode errors are handled.
If callback then errors result in a handle_answer/4 callback in the same fashion as for handle_request/3, with errors communicated in the errors field of the #diameter_packet{} passed to the callback.
If report then an answer containing errors is discarded without a callback and a warning report is written to the log.
If discard then an answer containing errors is silently discarded without a callback.
In both the report and discard cases the return value for the call/4 invocation in question is as if a callback had taken place and returned {error, failure}.
Defaults to discard. ๐
{request_errors, answer_3xxx|answer|callback}โ 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.
If answer_3xxx then requests are answered without a handle_request/3 callback taking place.
If answer 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 callback then a handle_request/3 callback always takes place and its return value determines the answer sent to the peer, if any.
Defaults to answer_3xxx.
๐ NOTE: Answers sent bydiameterset the E-bit in the Diameter Header. Since RFC 3588 allows only 3xxx result codes in an answer-message,answerhas the same semantics asanswer_3xxxwhen the transport in question has been configured withdiameter_gen_base_rfc3588as its common dictionary. Since RFC 6733 allows both 3xxx and 5xxx result codes in an answer-message, a transport withdiameter_gen_base_rfc6733as its common dictionary does distinguish betweenanswer_3xxxandanswer. ๐
call_opt()โ Options available tocall/4when sending an outgoing Diameter request. Has one of the following types. ๐{extra, list()}โ Extra arguments to append to callbacks to the callback module in question. These are appended to any extra arguments configured on the callback itself. Multiple options append to the argument list. โ{filter, peer_filter()}โ Filter to apply to the list of available peers before passing it to thepick_peer/4callback for the application in question. Multiple options are equivalent a singleallfilter on the corresponding list of filters. Defaults tonone. ๐ฏ{peer, diameter_app:peer_ref()}โ Peer to which the request in question can be sent, preempting the selection of peers having advertised support for the Diameter application in question. Multiple options can be specified, and their order is respected in the candidate lists passed to a subsequentpick_peer/4callback. ๐ฏ{timeout, Unsigned32()}โ Number of milliseconds after which the request should timeout. Defaults to5000. โฑ๏ธdetachโ Causecall/4to returnokas soon as the request in question has been encoded, instead of waiting for and returning the result from a subsequenthandle_answer/4orhandle_error/4callback. ๐๏ธ
An invalid option will cause call/4 to fail. ๐ฅ
capability()โ AVP values sent in outgoing CER or CEA messages during capabilities exchange. Can be configured both on a service and a transport, values on the latter taking precedence. Has one of the following types. ๐{'Origin-Host', DiameterIdentity()}๐{'Origin-Realm', DiameterIdentity()}๐{'Host-IP-Address', [Address()]}โ An address list is available to thestartfunction of a transport module, which can return a new list for use in the subsequent CER or CEA.Host-IP-Addressneed not be specified if the transport module in question communicates an address list as described indiameter_transport. ๐ก{'Vendor-Id', Unsigned32()}๐ข{'Product-Name', UTF8String()}๐๏ธ{'Origin-State-Id', Unsigned32()}โ Origin-State-Id is optional but, if configured, will be included in outgoing CER/CEA and DWR/DWA messages. Setting a value of 0 (zero) is equivalent to not setting a value, as documented in RFC 6733. The functionorigin_state_id/0can be used as to retrieve a value that is computed when thediameterapplication is started. ๐{'Supported-Vendor-Id', [Unsigned32()]}๐ค{'Auth-Application-Id', [Unsigned32()]}๐{'Inband-Security-Id', [Unsigned32()]}โ Inband-Security-Id defaults to the empty list, which is equivalent to a list containing only 0 (NO_INBAND_SECURITY). If 1 (TLS) is specified then TLS is selected if the CER/CEA received from the peer offers it. ๐{'Acct-Application-Id', [Unsigned32()]}๐งพ{'Vendor-Specific-Application-Id', [Grouped()]}๐{'Firmware-Revision', Unsigned32()}๐ง
Note that each tuple communicates one or more AVP values. It is an error to specify duplicate tuples. ๐ซ
eval() = {M,F,A} | fun() | [eval() | A]โ An expression that can be evaluated as a function in the following sense.
eval([{M,F,A} | T]) ->
apply(M, F, T ++ A);
eval([[F|A] | T]) ->
eval([F | T ++ A]);
eval([F|A]) ->
apply(F, A);
eval(F) ->
eval([F]).
Applying an eval() E to an argument list A is meant in the sense of eval([E|A]). ๐งช
โ ๏ธ WARNING: Beware of using fun expressions of the formfun Name/Arityin situations in which the fun is not short-lived and code is to be upgraded at runtime since any processes retaining such a fun will have a reference to old code. In particular, such a value is typically inappropriate in configuration passed tostart_service/2oradd_transport/2. ๐
peer_filter() = term()โ Filter passed tocall/4in order to select candidate peers for apick_peer/4callback. Has one of the following types. ๐ต๏ธโโ๏ธnoneโ Matches any peer. This is a convenience that provides a filter equivalent to no filter. ๐hostโ Matches only those peers whose Origin-Host has the same value as Destination-Host in the outgoing request in question, or any peer if the request does not contain a Destination-Host AVP. ๐realmโ Matches only those peers whose Origin-Realm has the same value as Destination-Realm in the outgoing request in question, or any peer if the request does not contain a Destination-Realm AVP. ๐{host, any|DiameterIdentity()}โ Matches only those peers whose Origin-Host has the specified value, or all peers if the atomany. ๐{realm, any|DiameterIdentity()}โ Matches only those peers whose Origin-Realm has the specified value, or all peers if the atomany. ๐{eval, eval()}โ Matches only those peers for which the specifiedeval()returnstruewhen applied to the connection'sdiameter_capsrecord. Any other return value or exception is equivalent tofalse. โ{neg, peer_filter()}โ Matches only those peers not matched by the specified filter. ๐ซ{all, [peer_filter()]}โ Matches only those peers matched by each filter in the specified list. ๐งฉ
{any, [peer_filter()]}โ only the peers that match at least one filter in your list get through</p>. And the resulting list keeps match order, so the peers that matched the first filter sort before the ones matched by the second, and so on down the line. ๐{first, [peer_filter()]}โ same energy asany, except it stops dead at the first filter that actually has matches. Which is lowkey a huge glow-up in efficiency when you've got a whole crowd of peers. For example, this filter below means only the peers that best match both the host and realm filters get presented, periodt:
{first, [{all, [host, realm]}, realm]}
An invalid filter is equivalent to {any,[]}, which is a filter that matches no peer at all. ๐ซฅ
NOTE: thehostandrealmfilters make theDestination-HostandDestination-RealmAVPs get extracted from the outgoing request. It assumes that request is a record- or list-valueddiameter_codec:message(), and it assumes at most one of each AVP. If that's not your situation, bestie, you must use the{host|realm, DiameterIdentity()}filters to get the result you want. An emptyDiameterIdentity()(which shouldn't be typical, ngl) matches all hosts/realms as far as filtering is concerned.
WARNING: a host filter is typically not what you want when setting Destination-Host, because it will remove peer agents from the candidates list. ๐ญ
๐ง service_event()
service_event() = #diameter_event{service = service_name(), info =
service_event_info()}
An event message, sent to the processes that subscribed to these with subscribe/1. ๐
๐ service_event_info()
service_event_info() = term()
The info field of a service_event() record. It can be one of the following types. ๐
start,stopThe service is being started or stopped. No event precedes a start event. No event follows a stop event, and this event implies the termination of all transport processes. ๐ช{up, Ref, Peer, Config, Pkt}{up, Ref, Peer, Config}{down, Ref, Peer, Config}
Ref = transport_ref()
Peer = diameter_app:peer()
Config = {connect|listen, [transport_opt()]}
Pkt = #diameter_packet{}
The RFC 3539 watchdog state machine has transitioned into (up) or out of (down) the OKAY state. If a #diameter_packet{} is present in an up event, then there's been a capabilities exchange on a newly established transport connection and the record contains the received CER or CEA. โจ
Note that a single up or down event for a given peer corresponds to multiple peer_up/3 or peer_down/3 callbacks โ one for each of the Diameter applications negotiated during capabilities exchange. Like, the event communicates connectivity with the peer as a whole, while the callbacks communicate connectivity with respect to individual Diameter applications. Different vibes, same peer. ๐
{reconnect, Ref, Opts}
Ref = transport_ref()
Opts = [transport_opt()]
A connecting transport is trying to establish/reestablish a transport connection with a peer following connect_timer or watchdog_timer expiry. ๐
{closed, Ref, Reason, Config}
Ref = transport_ref()
Config = {connect|listen, [transport_opt()]}
Capabilities exchange has failed. ๐ต Reason can have one of the following types.
{'CER', Result, Caps, Pkt}
Result = ResultCode | {capabilities_cb, CB, ResultCode|discard}
Caps = #diameter_caps{}
Pkt = #diameter_packet{}
ResultCode = integer()
CB = eval()
An incoming CER has been answered with the indicated result code, or discarded. Caps contains pairs of values โ local node and remote peer respectively. Pkt contains the CER in question. If a capabilities callback did the rejecting, the tuple contains the rejecting callback. ๐
{'CER', Caps, {ResultCode, Pkt}}
ResultCode = integer()
Caps = #diameter_caps{}
Pkt = #diameter_packet{}
An incoming CER contained errors and has been answered with the indicated result code. Here Caps contains values for the local node only. Pkt contains the CER in question.
{'CER', timeout}An expected CER was not received withincapx_timeoutof connection establishment. โฐ{'CEA', Result, Caps, Pkt}
Result = ResultCode | atom() | {capabilities_cb, CB, ResultCode|discard}
Caps = #diameter_caps{}
Pkt = #diameter_packet{}
ResultCode = integer()
An incoming CEA has been rejected for the indicated reason. If Result is an integer, that's the result code the peer sent. Caps contains pairs of values for the local node and remote peer. Pkt contains the CEA in question. If a capabilities callback did the rejecting, the tuple contains the rejecting callback.
{'CEA', Caps, Pkt}
Caps = #diameter_caps{}
Pkt = #diameter_packet{}
An incoming CEA contained errors and has been rejected. Caps contains only values for the local node. Pkt contains the CEA in question.
{'CEA', timeout}An expected CEA was not received withincapx_timeoutof connection establishment. โณ
{watchdog, Ref, PeerRef, {From, To}, Config}
Ref = transport_ref()
PeerRef = diameter_app:peer_ref()
From, To = initial | okay | suspect | down | reopen
Config = {connect|listen, [transport_opt()]}
An RFC 3539 watchdog state machine has changed state. glow up or glow down, either way it's a state change ๐
any/0For forward compatibility, a subscriber should be prepared to receiveinfofields of forms other than the above. Bestie, stay flexible. ๐ฆ
๐ธ service_name()
service_name() = term()
Name of a service, as passed to start_service/2 and with which the service is identified. There can be at most one service with a given name on a given node. Note that erlang:make_ref/0 can be used to generate a service name that is somewhat unique. ๐ฒ
๐ service_opt()
Option passed to start_service/2. Can be any capability() as well as the following. ๐
{application, [application_opt()]}A Diameter application supported by the service.
A service must configure one tuple for each Diameter application it intends to support. For an outgoing request, the relevant application_alias() is passed to call/4. For an incoming request, the application identifier in the message header determines the application, and that identifier is specified in the application's dictionary file. ๐
WARNING: the capabilities advertised by a node must match its configured applications. In particular, application configuration must be matched by correspondingcapability()configuration, of*-Application-IdAVPs especially. ๐จ
{decode_format, record | list | map | none}The format of decoded messages and grouped AVPs in themsgfield ofdiameter_packetrecords and thevaluefield ofdiameter_avprecords respectively.recordโ a record whose definition is generated from the dictionary file in question.listormapโ a[Name | Avps]pair, whereAvpsis a list of AVP name/values pairs or a map keyed on AVP names, respectively.noneโ the atom-value message name, orundefinedfor a Grouped AVP.
See also diameter_codec:message().
Defaults to record. ๐๏ธ
NOTE: AVPs are decoded into a list ofdiameter_avprecords in theavpsfield ofdiameter_packetrecords independently ofdecode_format. So don't think that setting it to something else makes those go away, bestie.
{restrict_connections, false | node | nodes | [node()] | eval()}The degree to which the service allows multiple transport connections to the same peer, as identified by itsOrigin-Hostat capabilities exchange. ๐
If [node()] then a connection is rejected if another already exists on any of the specified nodes. The types false, node, nodes and eval() are equivalent to [], [node()], [node()|nodes()] and the evaluated value respectively โ and evaluation of each expression takes place whenever a new connection is to be established. Note that false allows an unlimited number of connections to be established with the same peer. ๐
Multiple connections are independent and governed by their own peer and watchdog state machines.
Defaults to nodes.
{sequence, {H,N} | eval()}A constant valueHfor the topmost32-Nbits of of 32-bit End-to-End and Hop-by-Hop Identifiers generated by the service, either explicitly or as a return value of a function to be evaluated atstart_service/2. In particular, an identifierIdis mapped to a new identifier as follows. ๐งฎ
(H bsl N) bor (Id band ((1 bsl N) - 1))
Note that RFC 6733 requires that End-to-End Identifiers remain unique for a period of at least 4 minutes, and that this plus the call rate places a lower bound on appropriate values of N: at a rate of R requests per second, an N-bit counter traverses all of its values in (1 bsl N) div (R*60) minutes, so the bound is 4*R*60 =< 1 bsl N. โจ
N must lie in the range 0..32 and H must be a non-negative integer less than 1 bsl (32-N).
Defaults to {0,32}.
WARNING: 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. Don't be the node that duplicates everybody else, it's giving chaotic. ๐
{share_peers, boolean() | [node()] | eval()}Nodes to which peer connections established on the local Erlang node are communicated. Shared peers become available in the remote candidates list passed topick_peer/4callbacks on remote nodes whose services are configured to use them: seeuse_shared_peersbelow. ๐ซถ
If false then peers are not shared. If [node()] then peers are shared with the specified list of nodes. If eval() 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 true is equivalent to fun erlang:nodes/0. The value node/0 in a list is ignored, so a collection of services can all be configured to share with the same list of nodes. ๐
Defaults to false.
{strict_arities, boolean() | encode | decode}โ do we make the number of AVPs in a message (or a grouped AVP) actually match what the dictionary says it should be, when we hand messages to thediameter_appcallbacks? That's the vibe check. If you set it totrue, then a mismatch in an outgoing message makes the encoding straight-up fail, and a mismatch in an incoming message gets reported as5005/5009errors in theerrorsfield of thediameter_packetrecord that shows up at yourhandle_request/3orhandle_answer/4callbacks. If you set it tofalse, neither of those gets enforced or detected. And if you set it toencodeordecode, the errors are only enforced/detected on outgoing or incoming messages respectively. ๐
Defaults to true.
NOTE: turning arity checks off changes the form of messages at encode/decode. Specifically, decoded AVPs come to you as lists of values no matter what the AVP's arity is (arity = the expected number of it in the grammar for that message/AVP), and at encode you're expected to supply values as lists too. This is not how decode historically behaved, where AVPs of arity 1 were represented as bare values โ no list wrapping. ๐ซ
{string_decode, boolean()}โ are we decoding AVPs of typeOctetString()and all its derived besties:DiameterIdentity(),DiameterURI(),IPFilterRule(),QoSFilterRule(), andUTF8String()? Iftrue, AVPs of these types decode tostring(). Iffalse, the values stay asbinary(). โจ
Defaults to true.
WARNING: bestie, set this to false. A sufficiently malicious peer can otherwise make you burn so much memory when decoded Diameter messages get passed between processes. The default is the way it is for backwards compatibility only. ๐ญ
{traffic_counters, boolean()}โ do we count application-specific messages, i.e. the ones wherediameter_appcallbacks actually happen? Iffalse, then only the messages handled by diameter itself get counted:CER/CEA,DWR/DWA,DPR/DPA. ๐ธ
Defaults to true.
NOTE: turning counters off is a performance glow-up, but it means the omitted counters are not returned by service_info/2.
{use_shared_peers, boolean() | [node()] | eval()}โ which nodes' communicated peers get made available in the remote candidates list of yourpick_peer/4callbacks. Iffalse, remote peers aren't used at all. If[node()], only peers from that specified list of nodes are used. Ifeval(), only the peers returned by the specified function are used, and that function gets evaluated whenever a remote service communicates info about an available peer connection. The valuetrueis equivalent tofun erlang:nodes/0. And the valuenode/0in a list is ignored. ๐
Defaults to false.
NOTE: a service that doesn't use shared peers will always pass the empty list as the second argument of pick_peer/4 callbacks.
WARNING: 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 use theservice_opt()restrict_connectionsand keep a dedicated connection on each node you send requests from. ๐
transport_opt()โ any transport option exceptapplications,capabilities,transport_config, andtransport_module. These act as defaults for transport configuration, and values passed toadd_transport/2override values configured on the service. ๐
โจ transport_opt() โจ
Option passed to add_transport/2. It has one of the following types, bestie:
{applications, [application_alias()]}โ which Diameter applications this transport should be restricted to. Defaults to every application configured on the service in question. Applications not configured on the service in question are just ignored. โจ
WARNING: the capabilities a node advertises have to match its configured applications. In particular, settingapplicationson a transport typically means you also have to set matching*-Application-IdAVPs in acapabilities()tuple. ๐ซถ
{avp_dictionaries, [module()]}โ a list of alternate dictionary modules to encode/decode AVPs that aren't defined by the dictionary of the application in question. At decode, such AVPs are represented asdiameter_avprecords in the'AVP'field of a decoded message or Grouped AVP, and the first alternate that succeeds at decoding the AVP sets the record's value field. At encode, values in an'AVP'list can be passed as AVP name/value 2-tuples, and it's an encode error if no alternate defines the AVP of such a tuple. ๐
Defaults to the empty list.
NOTE: the motivation for alternate dictionaries is RFC 7683, Diameter Overload Indication Conveyance (DOIC), which defines AVPs meant to be piggybacked onto existing application messages instead of defining an application of its own. The DOIC dictionary ships with the diameter application as module diameter_gen_doic_rfc7683, but alternate dictionaries can be used to encode/decode any set of AVPs not known to an application dictionary. ๐
{capabilities, [capability()]}โ AVPs used to construct outgoingCER/CEAmessages. These values take precedence over any specified on the service in question. Specifying a capability as a transport option may be particularly appropriate forInband-Security-Id, in case TLS is desired over TCP as implemented bydiameter_tcp. ๐{capabilities_cb, eval()}โ a callback invoked whenCER/CEAis received during capabilities exchange, so it can be asked whether the connection should be accepted or not. It gets applied to thetransport_ref()and the#diameter_caps{}record of the connection. ๐
The return value can have one of the following types.
okโ accept the connection. ๐ซถinteger/0โ causes an incomingCERto be answered with the specifiedResult-Code.discardโ causes an incomingCERto be discarded withoutCEAbeing sent.unknownโ equivalent to returning3010,DIAMETER_UNKNOWN_PEER.
Returning anything but ok or a 2xxx series result code causes the transport connection to be broken. You can specify multiple capabilities_cb options, in which case the corresponding callbacks are applied until either all of them return ok or one of them doesn't. ๐ญ
{capx_timeout, Unsigned32()}โ number of milliseconds after which a transport process with an established transport connection will be terminated if the expected capabilities exchange message (CERorCEA) hasn't been received from the peer. For a connecting transport, the timing of connection attempts is governed byconnect_timerorwatchdog_timerexpiry. For a listening transport, the peer determines the timing. โฐ
Defaults to 10000.
{connect_timer, Tc}
Tc = Unsigned32()
For a connecting transport, this is the RFC 6733 Tc timer, in milliseconds. This timer determines how often a transport tries to establish an initial connection with its peer following transport configuration. Once an initial connection has been established, watchdog_timer determines the frequency of reconnection attempts, as required by RFC 3539. ๐ฅ
For a listening transport, the timer specifies the time after which a previously connected peer will be forgotten: a connection after this time counts as an initial connection rather than a reestablishment, which makes the RFC 3539 state machine pass to state OKAY rather than REOPEN. Heads up though, these semantics are not governed by the RFC, and a listening transport's connect_timer should be greater than its peer's Tw plus jitter.
Defaults to 30000 for a connecting transport and 60000 for a listening transport. โจ
{disconnect_cb, eval()}โ a callback invoked before terminating the transport process of a transport connection having watchdog stateOKAY. Applied toapplication|service|transportand thetransport_ref()anddiameter_app:peer()in question:applicationmeans the diameter application is being stopped,servicemeans the service in question is being stopped bystop_service/1, andtransportmeans the transport in question is being removed byremove_transport/2. ๐ฆ
The return value can have one of the following types.
{dpr, [option()]}โ send Disconnect-Peer-Request to the peer, and the transport process is terminated once Disconnect-Peer-Answer is received or on timeout. Anoption()can be one of the following.{cause, 0|rebooting|1|busy|2|goaway}โ the Disconnect-Cause to send:REBOOTING,BUSYandDO_NOT_WANT_TO_TALK_TO_YOUrespectively. Defaults torebootingforReason=service|applicationandgoawayforReason=transport.{timeout, Unsigned32()}โ number of milliseconds after which the transport process is terminated ifDPAhasn't been received. Defaults to the value ofdpa_timeout.
dprโ equivalent to{dpr, []}.closeโ terminate the transport process without Disconnect-Peer-Request being sent to the peer. ๐ignoreโ equivalent to not having configured the callback at all.
You can specify multiple disconnect_cb options, in which case the corresponding callbacks are applied until one of them returns a value other than ignore. All callbacks returning ignore is equivalent to not having configured them. ๐
Defaults to a single callback returning dpr.
{dpa_timeout, Unsigned32()}โ number of milliseconds after which a transport connection is terminated following an outgoingDPRifDPAisn't received.
Defaults to 1000. โณ
{dpr_timeout, Unsigned32()}โ number of milliseconds after which a transport connection is terminated following an incomingDPRif the peer does not close the connection.
Defaults to 5000. โณ
{incoming_maxlen, 0..16777215}โ a bound on the expected size of incoming Diameter messages. Messages bigger than the specified number of bytes get discarded. ๐ซ
Defaults to 16777215, the maximum value of the 24-bit Message Length field in a Diameter Header. โจ
{length_errors, exit|handle|discard}โ bestie, what do we DO when the Message Length field in an incoming Diameter Header is giving wrong? ๐ญ An error here = the length is not at least 20 bytes (that's the length of a Header btw), OR it's not a multiple of 4 (which is what a valid length looks like), OR it's just not the actual length of the message in question as it came in over the transport interface documented indiameter_transport.
So your three choices:
exitโ the transport process in question literally exits ๐handleโ the message gets processed like normal, and if ahandle_request/3orhandle_answer/4callback happens to fire, it'll flag the 5015 error (DIAMETER_INVALID_MESSAGE_LENGTH)discardโ the message in question is silently yeeted, no note, no nothing ๐
Defaults to exit.
NOTE: the default is like that on purpose, because a transport module for a stream-oriented transport like TCP might not be able to recover from a message length error at all. Why? Because that kind of transport has to use the Message Length header to chop 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 diameter_tcp for the behaviour of that module. ๐ซถ
{pool_size, pos_integer()}โ how many transport processes to start. For a listening transport, this sets the size of the pool of accepting transport processes โ and a bigger number is lowkey desirable when you're processing multiple concurrent peer connection attempts. For a connecting transport, it's the number of connections to the peer in question that will be attempted to be established. Heads up though:service_opt(): restrict_connectionsshould also be configured on the service in question if you want to allow multiple connections to the same peer. ๐{spawn_opt, [term()] | {M,F,A}}โ an options list passed toerlang:spawn_opt/2to spawn a handler process for an incoming Diameter request on the local node, or an MFA that returns the pid of a handler process. ๐ฃ
The monitor and link options are ignored in the list-valued case, jsyk. An MFA is applied with an additional term prepended to its argument list, and it should return either the pid of the handler process that invokes diameter_traffic:request/1 on the argument in order to process the request, or the atom discard. The handler process need not be local, and diameter need not be started on the remote node โ but diameter and relevant application callbacks must be on the code path. No cap. ๐
Defaults to the empty list.
{strict_capx, boolean()]}โ whether or not to enforce the RFC 6733 requirement that any message before capabilities exchange should close the peer connection. Iffalsethen unexpected messages are just discarded. ๐ฎ
Defaults to true. Changing this = non-standard behaviour, but it can be useful in case peers are known to behave badly. ๐
{strict_mbit, boolean()}โ whether or not to regard an AVP setting the M-bit as erroneous when the command grammar in question does not explicitly allow the AVP. Iftruethen such AVPs are regarded as 5001 errors,DIAMETER_AVP_UNSUPPORTED. Iffalsethen the M-bit is ignored and policing it becomes the receiver's responsibility. โจ
Defaults to true.
WARNING: RFC 6733 is unclear about the semantics of the M-bit. On the one hand, the CCF specification in section 3.2 documents AVP in a command grammar as meaning any arbitrary AVP; on the other hand, 1.3.4 states that AVPs setting the M-bit cannot be added to an existing command: the modified command must instead be placed in a new Diameter application. The reason for the latter is presumably interoperability: allowing arbitrary AVPs setting the M-bit in a command makes its interpretation implementation-dependent, since there's no guarantee that all implementations will understand the same set of arbitrary AVPs in the context of a given command. However, interpreting AVP in a command grammar as any AVP, regardless of M-bit, renders 1.3.4 meaningless, since the receiver can simply ignore any AVP it thinks isn't relevant, regardless of the sender's intent. Beware of confusing mandatory in the sense of the M-bit with mandatory in the sense of the command grammar. The former is a semantic requirement: that the receiver understand the semantics of the AVP in the context in question. The latter is a syntactic requirement: whether or not the AVP must occur in the message in question. ๐ตโ๐ซ
{transport_config, term()}{transport_config, term(), Unsigned32() | infinity}โ a term passed as the third argument to thestart/3function of the relevant transport module in order to start a transport process. Defaults to the empty list. ๐ธ
The 3-tuple form additionally specifies an interval, in milliseconds, after which a started transport process should be terminated if it has not yet established a connection. For example, the following options on a connecting transport request a connection with one peer over SCTP or another (typically the same) over TCP.
{transport_module, diameter_sctp}
{transport_config, SctpOpts, 5000}
{transport_module, diameter_tcp}
{transport_config, TcpOpts}
To listen on both SCTP and TCP, define one transport for each. ๐ฆ
{transport_module, atom()}โ module implementing a transport process as defined indiameter_transport. Defaults todiameter_tcp. ๐
Multiple transport_module and transport_config options are allowed. The order of these is significant in this case (and only in this case), a transport_module being paired with the first transport_config following it in the options list, or the default value for trailing modules. Transport starts will be attempted with each of the modules in order until one establishes a connection within the corresponding timeout (see below) or all fail. ๐ฅ
{watchdog_config, [{okay|suspect, non_neg_integer()}]}โ configuration that alters the behaviour of the watchdog state machine. On keyokay, the non-negative number of answered DWR messages before transitioning fromREOPENtoOKAY. On keysuspect, the number of watchdog timeouts before transitioning fromOKAYtoSUSPECTwhen DWR is unanswered, or0to not make the transition. โฐ
Defaults to [{okay, 3}, {suspect, 1}]. Not specifying a key is equivalent to specifying the default value for that key. ๐
WARNING: 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.
{watchdog_timer, TwInit}
TwInit = Unsigned32()
| {M,F,A}
The RFC 3539 watchdog timer. An integer value is interpreted as the RFC's TwInit in milliseconds, a jitter of ยฑ 2 seconds being added at each rearming of the timer to compute the RFC's Tw. An MFA is expected to return the RFC's Tw directly, with jitter applied, allowing the jitter calculation to be performed by the callback. ๐
An integer value must be at least 6000 as required by RFC 3539. Defaults to 30000.
Unrecognized options are silently ignored but are returned unmodified by service_info/2 and can be referred to in predicate functions passed to remove_transport/2. ๐
transport_ref() = reference()
Reference returned by add_transport/2 that identifies the configuration. โญ
๐ SEE ALSO ๐
diameter_app, diameter_transport, diameter_dict(4) ๐ซถ๐
โจ DATA TYPES โจ
okay bestie, before we get into the functions, here's the type tea โ. These are the exact Erlang type definitions, so don't @ me if they look a little formal. They're the blueprint for everything. Keep 'em close. ๐
'Address'() :: inet:ip_address() | string().
'DiameterIdentity'() :: 'OctetString'().
'DiameterURI'() :: 'OctetString'().
'Enumerated'() :: 'Integer32'().
'Float32'() :: '-infinity' | float() | infinity.
'Float64'() :: '-infinity' | float() | infinity.
'Grouped'() :: list() | tuple().
'IPFilterRule'() :: 'OctetString'().
'Integer32'() :: -2147483647..2147483647.
'Integer64'() :: -9223372036854775807..9223372036854775807.
'OctetString'() :: iolist().
'QoSFilterRule'() :: 'OctetString'().
'Time'() :: {{integer(), 1..12, 1..31}, {0..23, 0..59, 0..59}}.
'UTF8String'() :: iolist().
'Unsigned32'() :: 0..4294967295.
'Unsigned64'() :: 0..18446744073709551615.
app_alias() :: any().
app_module() ::
module() |
maybe_improper_list(module(), list()) |
#diameter_callback{}.
application_opt() ::
{alias, app_alias()} |
{answer_errors, callback | report | discard} |
{call_mutates_state, boolean()} |
{dictionary, module()} |
{module, app_module()} |
{request_errors, answer_3xxx | answer | callback} |
{state, any()}.
call_opt() ::
detach |
{extra, list()} |
{filter, peer_filter()} |
{peer, peer_ref()} |
{timeout, 'Unsigned32'()}.
capability() ::
{'Origin-Host', 'DiameterIdentity'()} |
{'Origin-Realm', 'DiameterIdentity'()} |
{'Host-IP-Address', ['Address'()]} |
{'Vendor-Id', 'Unsigned32'()} |
{'Product-Name', 'UTF8String'()} |
{'Supported-Vendor-Id', ['Unsigned32'()]} |
{'Auth-Application-Id', ['Unsigned32'()]} |
{'Vendor-Specific-Application-Id', ['Grouped'()]} |
{'Firmware-Revision', 'Unsigned32'()}.
common_opt() ::
{avp_dictionaries, [module()]} |
{capabilities_cb, eval()} |
{capx_timeout, 'Unsigned32'()} |
{connect_timer, 'Unsigned32'()} |
{disconnect_cb, eval()} |
{dpa_timeout, 'Unsigned32'()} |
{dpr_timeout, 'Unsigned32'()} |
{incoming_maxlen, message_length()} |
{length_errors, exit | handle | discard} |
{pool_size, pos_integer()} |
{spawn_opt, list() | mfa()} |
{strict_capx, boolean()} |
{strict_mbit, boolean()} |
{watchdog_config, [{okay | suspect, non_neg_integer()}]} |
{watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}.
decode_format() :: record | list | map | none | record_from_map.
elapsed_time() ::
{Hours :: non_neg_integer(),
Mins :: 0..59,
Secs :: 0..59,
MicroSecs :: 0..999999}.
eval() ::
{module(), atom(), list()} |
fun() |
maybe_improper_list(eval(), list()).
evaluable() :: eval().
message_length() :: 0..16777215.
peer_filter() ::
none | host | realm |
{host, any | 'DiameterIdentity'()} |
{realm, any | 'DiameterIdentity'()} |
{eval, eval()} |
{neg, peer_filter()} |
{first, [peer_filter()]} |
{all, [peer_filter()]} |
{any, [peer_filter()]}.
-opaque peer_ref()
remotes() :: boolean() | [node()] | eval().
restriction() :: false | node | nodes | [node()] | eval().
sequence() :: {'Unsigned32'(), 0..32}.
service_name() :: any().
service_opt() ::
capability() |
{application, [application_opt()]} |
{decode_format, decode_format()} |
{restrict_connections, restriction()} |
{sequence, sequence() | eval()} |
{share_peers, remotes()} |
{strict_arities, true | strict_arities()} |
{string_decode, boolean()} |
{traffic_counters, boolean()} |
{use_shared_peers, remotes()} |
{bins_info, boolean() | non_neg_integer()} |
common_opt().
strict_arities() :: false | encode | decode.
transport_opt() ::
{applications, [app_alias()]} |
{capabilities, [capability()]} |
{transport_config, any()} |
{transport_config, any(), 'Unsigned32'() | infinity} |
{transport_module, atom()} |
common_opt() |
{private, any()}.
transport_pred() ::
fun((transport_ref(), connect | listen, list()) -> boolean())
|
fun((transport_ref(), list()) -> boolean()) |
fun((list()) -> boolean()) |
transport_ref() |
boolean() |
list() |
{connect | listen, transport_pred()} |
{atom(), atom(), list()}.
transport_ref() :: reference().
That's a whole lot of types, but they're lowkey iconic. โจ
โจ FUNCTIONS โจ
add_transport/2 โ let's get connected ๐
add_transport(SvcName, Transport) -> {ok, TRef} | {error, Reason}
when
SvcName :: service_name(),
Transport :: {T, Opts},
T :: listen | connect,
Opts :: [transport_opt()],
TRef :: transport_ref(),
Reason :: term().
okay so this is how you add a transport to a service. The service will spin up transport procs as needed to connect to the peer โ either by reaching out (connect) or by accepting incoming requests (listen). A connecting transport links up with at most one peer, while a listening transport can handle many. ๐
The diameter app handles CER/CEA exchange with the peer. Once capabilities exchange succeeds, the service calls each app module's peer_up/3 callback, and after that you can send Diameter messages to the peer over the transport. Besides CER/CEA, the service also takes care of DWR/DWA as required by RFC 3539, and DPR/DPA. ๐ฅ
The returned reference is your unique ticket for that transport within the service. Heads up: the function returns before any transport connection is actually established. So don't expect instant connection, bestie. ๐ญ
NOTE: It is not an error to add a transport to a service that has not yet been configured: a service can be started after configuring its transports.
call/4 โ slide into their DMs with a request ๐
call(SvcName, App, Request, CallOpts) -> Result
when
SvcName :: service_name(),
App :: app_alias(),
Request ::
diameter_codec:message() |
diameter_codec:packet(),
CallOpts :: [call_opt()],
Result :: ok | {error, Reason} | Answer,
Answer :: term(),
Reason :: term().
okay so you wanna send a Diameter request? Say less. ๐
App tells you which Diameter application the request is defined in, and callbacks go to the corresponding callback module as described below and in diameter_app. Unless you pass the detach option, the call returns either when an answer message comes back from the peer or when an error happens. If you get an answer, the return value is whatever a handle_answer/4 callback returns. If it's an error, whether that error comes straight from diameter or from a handle_error/4 callback depends on if the outgoing request was successfully encoded for transmission to the peer โ the cases are below, bestie.
If there are no suitable peers, or if pick_peer/4 rejects them by returning false, then you get {error,no_connection}. Otherwise pick_peer/4 is followed by a prepare_request/3 callback, the message gets encoded, and then it's sent. ๐
There are several error cases that can stop an answer from being received and passed to a handle_answer/4 callback:
- If the initial encode of the outgoing request fails, then the request process fails and
{error,encode}is returned. - If the request is successfully encoded and sent but the answer times out then a
handle_error/4callback takes place withReason = timeout. - If the request is successfully encoded and sent but the service in question is stopped before an answer is received then a
handle_error/4callback takes place withReason = cancel. - If the transport connection with the peer goes down after the request has been sent but before an answer has been received then an attempt is made to resend the request to an alternate peer. If no such peer is available, or if the subsequent
pick_peer/4callback rejects the candidates, then ahandle_error/4callback takes place withReason = failover. If a peer is selected then aprepare_retransmit/3callback takes place, after which the semantics are the same as following an initialprepare_request/3callback. - If an encode error takes place during retransmission then the request process fails and
{error,failure}is returned. - If an application callback made in processing the request fails (
pick_peer,prepare_request,prepare_retransmit,handle_answerorhandle_error) then either{error,encode}or{error,failure}is returned depending on whether or not there has been an attempt to send the request over the transport.
Note that {error,encode} is the only return value which guarantees that the request has not been sent over the transport connection.
origin_state_id/0 โ time stamp flex โฐ
origin_state_id() -> 'Unsigned32'().
This one returns a reasonable value to use as Origin-State-Id in outgoing messages. ๐
The value is literally the number of seconds since 19680120T031408Z โ that's the first value that can be encoded as a Diameter Time() โ counted from when the diameter application was started. Time is fake but this timestamp is real. โฐ
remove_transport/2 โ bye bye transport ๐
remove_transport(SvcName, Pred) -> ok | {error, Reason}
when
SvcName :: service_name(),
Pred :: transport_pred(),
Reason :: term().
Remove previously added transports. It's giving cleanup era. ๐งน
Okay so Pred is the whole vibe-check of "which transports are we yeeting?" ๐๐
An arity-3 Pred removes every transport where Pred(Ref, Type, Opts) comes back true, where Type and Opts are literally the ones you passed to add_transport/2 and Ref is the one it handed back to you. Every other form of Pred is just a glow-up of an arity-3 fun, like this:
Pred = fun(transport_ref(), list()): fun(Ref, _, Opts) -> Pred(Ref, Opts) end
Pred = fun(list()): fun(_, _, Opts) -> Pred(Opts) end
Pred = transport_ref(): fun(Ref, _, _) -> Pred == Ref end
Pred = list(): fun(_, _, Opts) -> [] == Pred -- Opts end
Pred = true: fun(_, _, _) -> true end
Pred = false: fun(_, _, _) -> false end
Pred = {M,F,A}: fun(Ref, Type, Opts) -> apply(M, F, [Ref, Type, Opts | A]) end
Removing a transport means the transport processes that go with it get terminated, periodt. ๐ Whether or not a DPR message gets shipped off to the peer is fully controlled by whatever disconnect_cb you configured on that transport โ so yes, that setting is the main character here. ๐
service_info(SvcName, Item | [Item]) -> term()
when
SvcName :: service_name(),
Item :: atom() | peer_ref().
Gives you the tea โ on a started service. Ask for info about a service that doesn't exist and you just get undefined back โ no drama, no crash, just nothing. ๐ซฅ Ask for a list of items instead of one, and you get a tagged list back.
Item can be any one of these, bestie:
'Origin-Host''Origin-Realm''Vendor-Id''Product-Name''Origin-State-Id''Host-IP-Address''Supported-Vendor''Auth-Application-Id''Inband-Security-Id''Acct-Application-Id''Vendor-Specific-Application-Id'
All of these ones just hand you back a capability value as configured with start_service/2. โจ
applicationsโ Returns the list of applications as configured withstart_service/2.capabilitiesโ Returns a tagged list of all the capabilities values as configured withstart_service/2.transportโ Returns a list with one entry for each of the service's transport as configured withadd_transport/2. Every entry is a tagged list holding both the config and the info about peer connections that actually got established. A client service withOrigin-Host"client.example.com"configured with a single transport connected to"server.example.com"might spit out something like this:
[[{ref,#Ref<0.0.0.93>},
{type,connect},
{options,[{transport_module,diameter_tcp},
{transport_config,[{ip,{127,0,0,1}},
{raddr,{127,0,0,1}},
{rport,3868},
{reuseaddr,true}]}]},
{watchdog,{<0.66.0>,-576460736368485571,okay}},
{peer,{<0.67.0>,-576460736357885808}},
{apps,[{0,common}]},
{caps,[{origin_host,{"client.example.com","server.example.com"}},
{origin_realm,{"example.com","example.com"}},
{host_ip_address,{[{127,0,0,1}],[{127,0,0,1}]}},
{vendor_id,{0,193}},
{product_name,{"Client","Server"}},
{origin_state_id,{[],[]}},
{supported_vendor_id,{[],[]}},
{auth_application_id,{[0],[0]}},
{inband_security_id,{[],[0]}},
{acct_application_id,{[],[]}},
{vendor_specific_application_id,{[],[]}},
{firmware_revision,{[],[]}},
{avp,{[],[]}}]},
{port,[{owner,<0.69.0>},
{module,diameter_tcp},
{socket,{{127,0,0,1},48758}},
{peer,{{127,0,0,1},3868}},
{statistics,[{recv_oct,656},
{recv_cnt,6},
{recv_max,148},
{recv_avg,109},
{recv_dvi,19},
{send_oct,836},
{send_cnt,6},
{send_max,184},
{send_avg,139},
{send_pend,0}]}]},
{statistics,[{{{0,258,0},recv},3},
{{{0,258,1},send},3},
{{{0,258,0},recv,{'Result-Code',2001}},3},
{{{0,257,0},recv},1},
{{{0,257,1},send},1},
{{{0,257,0},recv,{'Result-Code',2001}},1},
{{{0,280,1},recv},2},
{{{0,280,0},send},2},
{{{0,280,0},send,{'Result-Code',2001}},2}]]]
Okay so lemme break down every single one of those keys, because there's a lot going on and it all eats. ๐ฅ
refis atransport_ref(), andoptionsis the matchingtransport_opt()list you passed intoadd_transport/2.- The
watchdogentry shows the state of a connection's RFC 3539 watchdog state machine. ๐ - The
peerentry identifies thediameter_app:peer_ref()โ the one for which there will have beenpeer_up/3callbacks for the Diameter applications named in theappsentry, withcommonbeing theapplication_alias(). - The
capsentry identifies the capabilities the local node sent and the ones it received from the peer during capabilities exchange. - The
portentry is the socket-level tea about the transport connection. ๐ต - The
statisticsentry is Diameter-level counters. An entry like{{{0,280,1},recv},2}is saying the client has received 2 DWR messages, because{0,280,1}={Application_Id, Command_Code, R_Flag}. Math queen. ๐งฎโจ
Heads up bestie: thewatchdog,peer,apps,capsandportentries all depend on whether you've actually got connectivity with the peer, so they might just not be there. Also, thestatisticsentry shows values accumulated over the lifetime of the transport configuration โ not a fresh count, an eternal one. โณ
A listening transport shows its info slightly differently, because there can be multiple accepted connections hanging off the same transport_ref(). The transport info a server returns with a single client connection might look like:
[[{ref,#Ref<0.0.0.61>},
{type,listen},
{options,[{transport_module,diameter_tcp},
{transport_config,[{reuseaddr,true},
{ip,{127,0,0,1}},
{port,3868}]}]},
{accept,[[{watchdog,{<0.56.0>,-576460739249514012,okay}},
{peer,{<0.58.0>,-576460638229179167}},
{apps,[{0,common}]},
{caps,[{origin_host,{"server.example.com","client.example.com"}},
{origin_realm,{"example.com","example.com"}},
{host_ip_address,{[{127,0,0,1}],[{127,0,0,1}]}},
{vendor_id,{193,0}},
{product_name,{"Server","Client"}},
{origin_state_id,{[],[]}},
{supported_vendor_id,{[],[]}},
{auth_application_id,{[0],[0]}},
{inband_security_id,{[],[]}},
{acct_application_id,{[],[]}},
{vendor_specific_application_id,{[],[]}},
{firmware_revision,{[],[]}},
{avp,{[],[]}}]},
{port,[{owner,<0.62.0>},
{module,diameter_tcp},
{socket,{{127,0,0,1},3868}},
{peer,{{127,0,0,1},48758}},
{statistics,[{recv_oct,1576},
{recv_cnt,16},
{recv_max,184},
{recv_avg,98},
{recv_dvi,26},
{send_oct,1396},
{send_cnt,16},
{send_max,148},
{send_avg,87},
{send_pend,0}]}]}],
[{watchdog,{<0.72.0>,-576460638229717546,initial}}]]},
{statistics,[{{{0,280,0},recv},7},
{{{0,280,1},send},7},
{{{0,280,0},recv,{'Result-Code',2001}},7},
{{{0,258,1},recv},3},
{{{0,258,0},send},3},
{{{0,258,0},send,{'Result-Code',2001}},3},
{{{0,280,1},recv},5},
{{{0,280,0},send},5},
{{{0,280,0},send,{'Result-Code',2001}},5},
{{{0,257,1},recv},1},
{{{0,257,0},send},1},
{{{0,257,0},send,{'Result-Code',2001}},1}]]]
Everything in there is exactly like the connect case, fr fr โ the only plot twist is that the client connections get grouped up under an accept tuple. ๐ซถ
And one last thing that changes the shape of your listing: whether or not the transport_opt() pool_size has been configured. If that value is greater than 1, that implies multiple transport processes for the same transport_ref(), just like in the listening case. ๐ The format there is similar to the listening case too โ it just has a pool tuple where the accept tuple would be. Swap and slay. ๐
connections ๐
okay bestie, so connections gives you a list with one entry for every established transport connection whose watchdog state machine is not in the down state. It's a flat view of transport info that lists only active connections, and Diameter-level statistics are accumulated only for the lifetime of the transport connection. A return value for the server above might look like this:
[[{ref,#Ref<0.0.0.61>},
{type,accept},
{options,[{transport_module,diameter_tcp},
{transport_config,[{reuseaddr,true},
{ip,{127,0,0,1}},
{port,3868}]}]},
{watchdog,{<0.56.0>,-576460739249514012,okay}},
{peer,{<0.58.0>,-576460638229179167}},
{apps,[{0,common}]},
{caps,[{origin_host,{"server.example.com","client.example.com"}},
{origin_realm,{"example.com","example.com"}},
{host_ip_address,{[{127,0,0,1}],[{127,0,0,1}]}},
{vendor_id,{193,0}},
{product_name,{"Server","Client"}},
{origin_state_id,{[],[]}},
{supported_vendor_id,{[],[]}},
{auth_application_id,{[0],[0]}},
{inband_security_id,{[],[]}},
{acct_application_id,{[],[]}},
{vendor_specific_application_id,{[],[]}},
{firmware_revision,{[],[]}},
{avp,{[],[]}}]},
{port,[{owner,<0.62.0>},
{module,diameter_tcp},
{socket,{{127,0,0,1},3868}},
{peer,{{127,0,0,1},48758}},
{statistics,[{recv_oct,10124},
{recv_cnt,132},
{recv_max,184},
{recv_avg,76},
{recv_dvi,9},
{send_oct,10016},
{send_cnt,132},
{send_max,148},
{send_avg,75},
{send_pend,0}]}]},
{statistics,[{{{0,280,0},recv},62},
{{{0,280,1},send},62},
{{{0,280,0},recv,{'Result-Code',2001}},62},
{{{0,258,1},recv},3},
{{{0,258,0},send},3},
{{{0,258,0},send,{'Result-Code',2001}},3},
{{{0,280,1},recv},66},
{{{0,280,0},send},66},
{{{0,280,0},send,{'Result-Code',2001}},66},
{{{0,257,1},recv},1},
{{{0,257,0},send},1},
{{{0,257,0},send,{'Result-Code',2001}},1}]}]]
๐ lowkey important: there may be multiple entries with the sameref, in contrast totransportinfo.
statistics ๐
Return a {Ref, [{Counter, non_neg_integer()}]} list of references and corresponding counter values. Ref can be either a transport_ref() or a diameter_app:peer_ref(). Entries for the latter are folded into corresponding entries for the former as peer connections go down. Entries for both are removed at remove_transport/2. The Diameter-level statistics returned by transport and connections info are based upon these entries.
diameter_app:peer_ref() ๐ง
Return transport configuration associated with a single peer, as passed to add_transport/2. The returned list is empty if the peer is unknown. Otherwise it contains the ref, type and options tuples as in transport and connections info above. For example:
[{ref,#Ref<0.0.0.61>},
{type,accept},
{options,[{transport_module,diameter_tcp},
{transport_config,[{reuseaddr,true},
{ip,{127,0,0,1}},
{port,3868}]}]}]
services() โจ
services() -> [SvcName] when SvcName :: service_name().
Return the list of started services.
session_id(Ident) ๐ธ
session_id(Ident) -> SessionId
when
Ident :: 'DiameterIdentity'(),
SessionId :: 'OctetString'().
Return a value for a Session-Id AVP.
The value has the form required by section 8.8 of RFC 6733. Ident should be the Origin-Host of the peer from which the message containing the returned value will be sent.
start() ๐ฅ
start() -> ok | {error, Reason} when Reason :: term().
Start the diameter application.
The diameter application must be started before starting a service. In a production system this is typically accomplished by a boot file, not by calling start/0 explicitly.
start_service(SvcName, Opts) ๐
start_service(SvcName, Opts) -> ok | {error, Reason}
when
SvcName :: service_name(),
Opts :: [service_opt()],
Reason :: term().
Start a diameter service.
A service defines a locally-implemented Diameter node, specifying the capabilities to be advertised during capabilities exchange. Transports are added to a service using add_transport/2.
NOTE: A transport can both override its service's capabilities and restrict its supported Diameter applications so "service = Diameter node as identified by Origin-Host" is not necessarily the case.
stop() ๐
stop() -> ok | {error, Reason} when Reason :: term().
Stop the diameter application.
stop_service(SvcName) ๐
stop_service(SvcName) -> ok | {error, Reason}
when SvcName :: service_name(), Reason :: term().
Stop a diameter service.
Stopping a service causes all associated transport connections to be broken. A DPR message will be sent as in the case of remove_transport/2.
NOTE: Stopping a service does not remove any associated transports: remove_transport/2 must be called to remove transport configuration.
subscribe(SvcName) ๐ซถ
subscribe(SvcName) -> true when SvcName :: service_name().
Subscribe to service_event() messages from a service.
It is not an error to subscribe to events from a service that does not yet exist. Doing so before adding transports is required to guarantee the reception of all transport-related events.
unsubscribe(SvcName) ๐
unsubscribe(SvcName) -> true when SvcName :: service_name().
Unsubscribe to event messages from a service.
which_connections() ๐
which_connections() ->
[{SvcName,
[#{peer := PeerInfo,
wd := WDInfo,
peername :=
{inet:ip_address(),
inet:port_number()},
sockname :=
{inet:ip_address(),
inet:port_number()}}]}]
when
SvcName :: string(),
PeerInfo ::
#{pid := pid(),
uptime := elapsed_time()},
WDInfo ::
#{ref := reference(),
type := atom(),
pid := pid(),
state :=
diameter_service:wd_state(),
uptime := elapsed_time()}.
Return a list of all connections, grouped by the service they are associated with.
which_connections(SvcName) ๐
which_connections(SvcName) ->
[#{peer := PeerInfo,
wd := WDInfo,
peername :=
{inet:ip_address(),
inet:port_number()},
sockname :=
{inet:ip_address(),
inet:port_number()}}]
when
SvcName :: string(),
PeerInfo ::
#{pid := pid(),
uptime := elapsed_time()},
WDInfo ::
#{ref := reference(),
type := atom(),
pid := pid(),
state :=
diameter_service:wd_state(),
uptime := elapsed_time()}.
Return a list of connections associated with the service 'SvcName'.
which_transports() โญ
which_transports() ->
[#{ref := reference(),
type := atom(),
service := string()}].
Return a list of all transports.
which_transports(SvcName) ๐
which_transports(SvcName) ->
[#{ref := reference(), type := atom()}]
when SvcName :: string().
Return a list of transports associated with the service 'SvcName'.
which_watchdogs() ๐ฆ
which_watchdogs() ->
[#{ref := reference(),
type := atom(),
pid := pid(),
state := diameter_service:wd_state(),
peer := boolean() | pid(),
uptime := elapsed_time(),
service := SvcName}]
when SvcName :: string().
Return a list of all watchdogs.
which_watchdogs(SvcName) ->
[#{ref := reference(),
type := atom(),
pid := pid(),
state := diameter_service:wd_state(),
peer := boolean() | pid(),
uptime := elapsed_time()}]
when SvcName :: string().
okay bestie, this one's a little peek behind the curtain ๐ โ which_watchdogs(SvcName) hands you back a list of all the watchdogs chillin' with the service SvcName. so if you ever wanted to vibe check which lil guardians are on duty, this is your girlie ๐
each entry in that list is a map, and every map is giving the same iconic shape โจ:
refโ areference(), its little unique ID ๐typeโ anatom(), so you know what kind of watchdog it ispidโ thepid()doing the actual work ๐ฅstateโ adiameter_service:wd_state(), i.e. what headspace it's currently in ๐งpeerโ aboolean()or apid(), so it's either a yes/no situation or an actual processuptimeโ anelapsed_time(), aka how long it's been clocked in and thriving โฐ
and remember, SvcName has to be a string() โ no cap, the guard says so ๐
so yeah, that's the whole function. which_watchdogs/1 โ short, sweet, and lowkey essential whenever you need to see who's holding down the fort ๐ซถ
aaaand that's a wrap on diameter(3) bestie!! ๐โจ you made it all the way to the end, which is giving main character energy fr fr ๐ this whole page was brought to you by Ericsson AB, and it's diameter 2.7.2, living in section diameter(3) of the manual ๐ go forth and slay your distributed Erlang adventures, ily, mwah ๐
๐๐ฆ
๐ฆ where this page comes from
- nixos 26.05: erlang 29.1.2
- nixpkgs unstable: erlang 29.1.2
- ubuntu stonking: erlang-doc 1:29.0.6+dfsg-1 ยท Apache-2.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms