diameter(3erl)

๐Ÿ“ฆ
version 25.3.2.8 ยท 3 versions
, in debian bookworm, ubuntu noble

๐Ÿ’– NAME ๐Ÿ’–

diameter(3erl) โ€” Erlang Module Definition, and the diameter module is the Main API of the diameter application. Bestie, this is the one. โœจ

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

Okay so this module gives you the interface where a user can implement a Diameter node that sends and receives messages using the Diameter protocol as defined in RFC 6733. It's giving main character energy for your whole Diameter setup. ๐Ÿ’…

Basic usage consists of creating a representation of a locally implemented Diameter node and its capabilities with start_service/2, adding transport capability using add_transport/2, and sending Diameter requests and receiving Diameter answers with call/4. Incoming Diameter requests are communicated as callbacks to a diameter_app(3erl) callback modules as specified in the service configuration.

Beware the difference between diameter (not capitalized) and Diameter (capitalized). The former refers to the Erlang application named diameter whose main api is defined here, the latter to Diameter protocol in the sense of RFC 6733.

The diameter application must be started before calling most functions in this module.

๐ŸŒธ DATA TYPES ๐ŸŒธ

Here's the type roster, no cap. โœจ

  • Address()
  • DiameterIdentity()
  • Grouped()
  • OctetString()
  • Time()
  • Unsigned32()
  • UTF8String() โ€” Types corresponding to RFC 6733 AVP Data Formats. Defined in diameter_dict(5).

๐ŸŽ€ application_alias() = term() ๐ŸŽ€

Name identifying a Diameter application in service configuration. Passed to call/4 when sending requests defined by the application.

๐Ÿฆ‹ application_module() ๐Ÿฆ‹

application_module() = Mod | [Mod | ExtraArgs] | #diameter_callback{}

Mod = atom()
ExtraArgs = list()

Module implementing the callback interface defined in diameter_app(3erl), 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(3erl) 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 the dictionary option.
  • {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 in diameter_dict(5).
  • {module, application_module()} โ€” Callback module in which messages of the Diameter application are handled. See diameter_app(3erl) for the required interface and semantics.
  • {state, term()} โ€” Initial callback state. The prevailing state is passed to some diameter_app(3erl) callbacks, which can then return a new state. Defaults to the value of the alias option.
  • {call_mutates_state, true|false} โ€” Whether or not the pick_peer/4 application callback can modify the application state. Defaults to false.
Warning: pick_peer/4 callbacks are serialized when this option is true, 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 by diameter set the E-bit in the Diameter Header. Since RFC 3588 allows only 3xxx result codes in an answer-message, answer has the same semantics as answer_3xxx when the transport in question has been configured with diameter_gen_base_rfc3588 as its common dictionary. Since RFC 6733 allows both 3xxx and 5xxx result codes in an answer-message, a transport with diameter_gen_base_rfc6733 as its common dictionary does distinguish between answer_3xxx and answer.

๐ŸŒˆ call_opt() ๐ŸŒˆ

Options available to call/4 when 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 the pick_peer/4 callback for the application in question. Multiple options are equivalent to a single all filter on the corresponding list of filters. Defaults to none.
  • {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 subsequent pick_peer/4 callback.
  • {timeout, Unsigned32()} โ€” Number of milliseconds after which the request should timeout. Defaults to 5000.
  • detach โ€” Cause call/4 to return ok as soon as the request in question has been encoded, instead of waiting for and returning the result from a subsequent handle_answer/4 or handle_error/4 callback.

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 the start function of a transport module, which can return a new list for use in the subsequent CER or CEA. Host-IP-Address need not be specified if the transport module in question communicates an address list as described in diameter_transport(3erl)
  • {'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 function origin_state_id/0 can be used as to retrieve a value that is computed when the diameter application 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() ๐Ÿงš

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 form fun Name/Arity in 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 to start_service/2 or add_transport/2.

๐Ÿ“ peer_filter() = term() ๐Ÿ“

Filter passed to call/4 in order to select candidate peers for a pick_peer/4 callback. 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 atom any.
  • {realm, any|DiameterIdentity()} โ€” Matches only those peers whose Origin-Realm has the specified value, or all peers if the atom any.
  • {eval, eval()} โ€” Matches only those peers for which the specified eval() returns true when applied to the connection's diameter_caps record. Any other return value or exception is equivalent to false.
  • {neg, peer_filter()} โ€” the hater-blocker ๐Ÿ’… it matches only those peers that the filter you hand it does not match. opposite day, but make it fashion.
  • {all, [peer_filter()]} โ€” matches only those peers matched by each filter in the list you pass. everyone's gotta agree, no exceptions ๐Ÿซถ
  • {any, [peer_filter()]} โ€” matches only those peers matched by at least one filter in the list. and bestie, the resulting list comes back in match order: peers that matched the first filter sort before the ones matched by the second, and so on down the line. first come, first served energy โœจ
  • {first, [peer_filter()]} โ€” literally any but it stops at the first filter that has matches. that's it, that's the whole thing, and it's highkey so much more efficient when you've got a ton of peers. like, the following filter makes only the peers that best match both the host and realm filters show up:
{first, [{all, [host, realm]}, realm]}

An invalid filter is equivalent to {any,[]} โ€” which is the filter that matches no peer. so if you fumble it, you get nobody. ghosted by your own config ๐Ÿ˜ญ

Note: the host and realm filters cause the Destination-Host and Destination-Realm AVPs to be extracted from the outgoing request โ€” assuming it's a record- or list-valued diameter_codec:message(), and assuming at most one of each AVP. If that's not your situation then you gotta use the {host|realm, DiameterIdentity()} filters to get the result you actually want. And an empty DiameterIdentity() (which, like, shouldn't really be typical) matches all hosts/realms for the purposes of filtering.
Warning: a host filter is not typically desirable when setting Destination-Host, because it will remove peer agents from the candidates list. not the vibe you want ๐Ÿ”ฅ

service_event() = #diameter_event{service = service_name(), info = service_event_info()}:

An event message sent to processes that have subscribed to these using subscribe/1. ๐Ÿ’Œ

service_event_info() = term():

The info field of a service_event() record. Can have one of the following types. ๐Ÿ‘‡

  • start:
  • stop:

The 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 has been a capabilities exchange on a newly established transport connection, and the record contains the received CER or CEA. ๐Ÿ“ฆ

Note: 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. That is, the event communicates connectivity with the peer as a whole, while the callbacks communicate connectivity with respect to individual Diameter applications. two different zoom levels, iykyk ๐Ÿ”
  • {reconnect, Ref, Opts}:
Ref  = transport_ref()
Opts = [transport_opt()]

A connecting transport is attempting to establish/reestablish a transport connection with a peer following connect_timer or watchdog_timer expiry. it's giving "we lost connection, trying again" ๐Ÿ”

  • {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, for the local node and remote peer respectively. Pkt contains the CER in question. In the case of rejection by a capabilities callback, 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. Caps contains values for the local node only. Pkt contains the CER in question. local only, no peer tea ๐Ÿซ–

  • {'CER', timeout}:

An expected CER was not received within capx_timeout of connection establishment. they left us on read ๐Ÿ’€

  • {'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. An integer-valued Result indicates the result code sent by the peer. Caps contains pairs of values for the local node and remote peer. Pkt contains the CEA in question. In the case of rejection by a capabilities callback, 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 within capx_timeout of 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. glowed up from one state to another โœจ

  • any():

For forward compatibility, a subscriber should be prepared to receive info fields of forms other than the above. future-proofing, bestie ๐Ÿงš

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, while for an incoming request the application identifier in the message header determines the application, the identifier being 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 corresponding capability() configuration, of *-Application-Id AVPs in particular. mismatched vibes = bad vibes. keep them in sync or suffer ๐Ÿ’€
  • {decode_format, record | list | map | none} โ€” The format of decoded messages and grouped AVPs in the msg field of diameter_packet records and value field of diameter_avp records respectively. If record then a record whose definition is generated from the dictionary file in question. If list or map then a [Name | Avps] pair where Avps is a list of AVP name/values pairs or a map keyed on AVP names respectively. If none then the atom-value message name, or undefined for a Grouped AVP. See also diameter_codec:message().

Defaults to record. ๐ŸŽ€

Note: AVPs are decoded into a list of diameter_avp records in avps field of diameter_packet records independently of decode_format. so that part's non-negotiable, periodt.
  • {restrict_connections, false | node | nodes | [node()] | eval()} โ€” The degree to which the service allows multiple transport connections to the same peer, as identified by its Origin-Host at capabilities exchange.

If [node()] then a connection is rejected if another already exists on any of the specified nodes. Types false, node, nodes and eval() are equivalent to [], [node()], [node()|nodes()] and the evaluated value respectively, evaluation of each expression taking 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. they don't share, they don't compare ๐Ÿซถ

Defaults to nodes.

  • {sequence, {H,N} | eval()} โ€” A constant value H for the topmost 32-N bits 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 at start_service/2. In particular, an identifier Id is 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 and 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. do the math, bestie, it's mathing ๐Ÿงฎ

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 let your nodes twinning too hard on the ID space ๐Ÿ™ˆ
  • {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 to pick_peer/4 callbacks on remote nodes whose services are configured to use them: see use_shared_peers below. ๐Ÿค

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() in a list is ignored, so a collection of services can all be configured to share with the same list of nodes. sharing is caring, literally ๐Ÿ’œ

Defaults to false.

Note ๐Ÿซถ: Peers are only shared with services of the same name for the purpose of sending outgoing requests. Since the value of the application_opt() alias, passed to call/4, 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're distributing the implementation of a single Diameter node across multiple Erlang nodes.
  • {strict_arities, boolean() | encode | decode} โ€” this one is the vibe check for AVP counts ๐Ÿ’… Should the number of AVPs in a message or grouped AVP be required to agree with what the dictionary in question says, when messages get passed to diameter_app(3erl) callbacks?
    • If true, then mismatches in an outgoing message make message encoding fail ๐Ÿ˜ญ, while mismatches in an incoming message are reported as 5005/5009 errors in the errors field of the diameter_packet record passed to handle_request/3 or handle_answer/4 callbacks.
    • If false, then neither error is enforced/detected.
    • If encode or decode, then errors are only enforced/detected on outgoing or incoming messages respectively.
    • Defaults to true.
Note ๐Ÿงš: Disabling arity checks affects the form of messages at encode/decode. In particular, decoded AVPs are represented as lists of values, regardless of the AVP's arity (ie. expected number in the message/AVP grammar in question), and values are expected to be supplied as lists at encode. This differs from the historic decode behaviour of representing AVPs of arity 1 as bare values, not wrapped in a list.
  • {string_decode, boolean()} โ€” should AVPs of type OctetString() and its derived types DiameterIdentity(), DiameterURI(), IPFilterRule(), QoSFilterRule(), and UTF8String() be decoded? If true, AVPs of these types get decoded to string(). If false, the values stay as binary().
    • Defaults to true.
Warning โš ๏ธ: This option should be set to false since a sufficiently malicious peer can otherwise cause large amounts of memory to be consumed when decoded Diameter messages are passed between processes. The default value is for backwards compatibility. Not a slay. Set it to false, bestie ๐Ÿ™
  • {traffic_counters, boolean()} โ€” should application-specific messages be counted? Y'know, the ones where diameter_app(3erl) callbacks take place. If false, then only messages handled by diameter itself are counted: CER/CEA, DWR/DWA, DPR/DPA.
    • Defaults to true.
Note ๐ŸŒธ: Disabling counters is a performance improvement, but it means the omitted counters are not returned by service_info/2.
  • {use_shared_peers, boolean() | [node()] | eval()} โ€” which nodes make communicated peers available in the remote candidates list of pick_peer/4 callbacks? ๐Ÿฆ‹
    • If false, then remote peers are not used.
    • If [node()], then only peers from the specified list of nodes are used.
    • If eval(), then only peers returned by the specified function are used, evaluated whenever a remote service communicates information about an available peer connection.
    • The value true is equivalent to fun erlang:nodes/0. The value node() in a list is ignored.
    • Defaults to false.
Note ๐Ÿ“: A service that does not 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 make use of the service_opt() restrict_connections and maintain a dedicated connection on each node from which requests are sent.
  • transport_opt() โ€” literally any transport option except applications, capabilities, transport_config, and transport_module. Used as defaults for transport configuration; values passed to add_transport/2 override values configured on the service โœจ

๐ŸŒˆ transport_opt() ๐ŸŒˆ

Option passed to add_transport/2. It has one of the following types, bestie โ€” buckle up ๐ŸŽ€

  • {applications, [application_alias()]} โ€” Diameter applications to which the transport should be restricted. Defaults to all applications configured on the service in question. Applications not configured on the service in question are ignored.
Warning ๐Ÿ‘‘: The capabilities advertised by a node must match its configured applications. In particular, setting applications on a transport typically implies having to set matching *-Application-Id AVPs in a capabilities() tuple.
  • {avp_dictionaries, [module()]} โ€” a list of alternate dictionary modules to encode/decode AVPs that are not defined by the dictionary of the application in question ๐Ÿ’œ
    • At decode, such AVPs are represented as diameter_avp records in the 'AVP' field of a decoded message or Grouped AVP, with the first alternate that succeeds in decoding the AVP setting the record's value field.
    • At encode, values in an 'AVP' list can be passed as AVP name/value 2-tuples, and it is an encode error for no alternate to define 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 to be piggybacked onto existing application messages rather than defining an application of its own. The DOIC dictionary is provided by 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 outgoing CER/CEA messages. These values take precedence over any specified on the service in question ๐Ÿ”ฅ
    • Specifying a capability as a transport option may be particularly appropriate for Inband-Security-Id, in case TLS is desired over TCP as implemented by diameter_tcp(3erl).
  • {capabilities_cb, eval()} โ€” callback invoked upon reception of CER/CEA during capabilities exchange, to ask whether or not the connection should be accepted. Applied to the transport_ref() and #diameter_caps{} record of the connection.
    • The return value can have one of the following types:
      • ok โ€” accept the connection โœ…
      • integer() โ€” causes an incoming CER to be answered with the specified Result-Code.
      • discard โ€” causes an incoming CER to be discarded without CEA being sent.
      • unknown โ€” equivalent to returning 3010, DIAMETER_UNKNOWN_PEER.
    • Returning anything but ok or a 2xxx series result code causes the transport connection to be broken. Multiple capabilities_cb options can be specified, in which case the corresponding callbacks are applied until either all return ok or one does not.
  • {capx_timeout, Unsigned32()} โ€” number of milliseconds after which a transport process having an established transport connection will be terminated if the expected capabilities exchange message (CER or CEA) is not received from the peer. For a connecting transport, the timing of connection attempts is governed by connect_timer or watchdog_timer expiry. For a listening transport, the peer determines the timing.
    • Defaults to 10000.
  • {connect_timer, Tc} โ€” for a connecting transport, the RFC 6733 Tc timer, in milliseconds โœจ
{connect_timer, Tc}

Tc = Unsigned32()
  • This timer determines the frequency with which a transport attempts 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 is regarded as an initial connection rather than reestablishment, causing the RFC 3539 state machine to pass to state OKAY rather than REOPEN. Note that these semantics are not governed by the RFC, and that 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()} โ€” callback invoked prior to terminating the transport process of a transport connection having watchdog state OKAY. Applied to application|service|transport and the transport_ref() and diameter_app:peer() in question: application indicates that the diameter application is being stopped, service that the service in question is being stopped by stop_service/1, and transport that the transport in question is being removed by remove_transport/2.
    • The return value can have one of the following types:
      • {dpr, [option()]} โ€” send Disconnect-Peer-Request to the peer, with the transport process being terminated following reception of Disconnect-Peer-Answer or timeout. An option() can be one of the following:
        • {cause, 0|rebooting|1|busy|2|goaway} โ€” the Disconnect-Cause to send: REBOOTING, BUSY and DO_NOT_WANT_TO_TALK_TO_YOU respectively. Defaults to rebooting for Reason=service|application and goaway for Reason=transport.
        • {timeout, Unsigned32()} โ€” number of milliseconds after which the transport process is terminated if DPA has not been received. Defaults to the value of dpa_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.
    • Multiple disconnect_cb options can be specified, 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 outgoing DPR if DPA is not received ๐Ÿ˜ญ
    • Defaults to 1000.
  • {dpr_timeout, Unsigned32()} โ€” the number of milliseconds before a transport connection gets yeeted ๐Ÿ’€ after an incoming DPR, if the peer still hasn't closed the connection.

Defaults to 5000.

  • {incoming_maxlen, 0..16777215} โ€” the cap on how big we expect incoming Diameter messages to be. Anything bigger than this many bytes gets tossed in the bin. ๐Ÿ—‘๏ธ

Defaults to 16777215, which is literally the max value the 24-bit Message Length field in a Diameter Header can hold.

  • {length_errors, exit|handle|discard} โ€” how to deal with a broken Message Length field in the Diameter Header of an incoming message. And "broken" here means: the length isn't at least 20 bytes (that's a Header, bestie), isn't a multiple of 4 (which is what a valid length looks like), or just isn't the actual length of the message in question as it came in over the transport interface documented in diameter_transport(3erl).
    • If exit, the transport process in question exits. periodt.
    • If handle, the message gets processed as usual, and if a handle_request/3 or handle_answer/4 callback takes place it'll flag the 5015 error (DIAMETER_INVALID_MESSAGE_LENGTH).
    • If discard, the message is silently yeeted, no comment.

Defaults to exit.

Note: the default is doing a lot of heavy lifting here, and for good reason โ€” a transport module for a stream-oriented transport like TCP might literally not be able to recover from a message length error, 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 zero reliable way to find message boundaries again, which can nuke subsequent messages too. Go read diameter_tcp(3erl) for what that module actually does. ๐Ÿงš
  • {pool_size, pos_integer()} โ€” how many transport processes to spin up. For a listening transport, this sets the size of the pool of accepting transport processes, and a bigger number is what you want for handling multiple concurrent peer connection attempts. For a connecting transport, it sets how many connections to the peer in question we'll try to establish: you should also configure service_opt(): restrict_connections on the service in question so multiple connections to the same peer are even allowed. ๐Ÿ‘‘
  • {spawn_opt, [term()] | {M,F,A}} โ€” an options list passed to erlang:spawn_opt/2 to spawn a handler process for an incoming Diameter request on the local node, or an MFA that returns the pid of a handler process.

In the list-valued case, the monitor and link options are ignored. An MFA gets applied with an extra term prepended to its argument list, and it should return either the pid of the handler process โ€” which invokes diameter_traffic:request/1 on that argument to process the request โ€” or the atom discard. The handler process doesn't have to be local, and diameter doesn't need to be started on the remote node, but diameter and the relevant application callbacks must be on the code path. ๐Ÿ˜Œ

Defaults to the empty list.

  • {strict_capx, boolean()]} โ€” whether or not to enforce the RFC 6733 requirement that any message that shows up before capabilities exchange should close the peer connection. If false, unexpected messages just get discarded instead.

Defaults to true. Changing this gives you non-standard behaviour, but it's handy when you know a peer is out here behaving badly. ๐Ÿ™ˆ

  • {strict_mbit, boolean()} โ€” whether or not to treat an AVP that sets the M-bit as erroneous when the command grammar in question doesn't explicitly allow that AVP. If true, those AVPs are treated as 5001 errors, DIAMETER_AVP_UNSUPPORTED. If false, the M-bit is straight-up ignored and policing it becomes the receiver's problem.

Defaults to true.

Warning: RFC 6733 is honestly unclear about what the M-bit even means. 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 says AVPs that set the M-bit can't be added to an existing command โ€” instead the modified command has to live in a new Diameter application.
The reason for the latter is presumably interoperability: if you allow arbitrary AVPs that set the M-bit inside a command, interpreting that command becomes implementation-dependent, because there's no guarantee every implementation will understand the same set of arbitrary AVPs in the context of a given command. But if you interpret AVP in a command grammar as any AVP regardless of M-bit, then 1.3.4 means literally nothing, since the receiver can just ignore any AVP it thinks isn't relevant, whatever the sender intended. ๐Ÿ’€
Don't go confusing mandatory in the M-bit sense with mandatory in the command grammar sense. The first one is a semantic requirement: that the receiver understands the semantics of the AVP in the context in question. The second one is a syntactic requirement: whether or not the AVP has to occur in the message in question. Two totally different vibes.
  • {transport_config, term()}
  • {transport_config, term(), Unsigned32() | infinity} โ€” a term passed as the third argument to the start/3 function of the relevant transport module, so it can start a transport process. Defaults to the empty list.

The 3-tuple form also specifies an interval, in milliseconds, after which a started transport process should be terminated if it hasn't established a connection yet. So like, the following options on a connecting transport ask for a connection to one peer over SCTP, or another (usually the same) peer over TCP:

{transport_module, diameter_sctp}
{transport_config, SctpOpts, 5000}
{transport_module, diameter_tcp}
{transport_config, TcpOpts}

Wanna listen on both SCTP and TCP? Define one transport for each. ๐Ÿซถ

  • {transport_module, atom()} โ€” the module implementing a transport process as defined in diameter_transport(3erl). Defaults to diameter_tcp.

Multiple transport_module and transport_config options are allowed. In that case (and only that case) the order of these is significant: a transport_module gets paired with the first transport_config that follows it in the options list, or with the default value for any 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 until they all fail. ๐Ÿ˜ญ

  • {watchdog_config, [{okay|suspect, non_neg_integer()}]} โ€” config that alters the behaviour of the watchdog state machine. On key okay: the non-negative number of answered DWR messages before transitioning from REOPEN to OKAY. On key suspect: the number of watchdog timeouts before transitioning from OKAY to SUSPECT when DWR goes unanswered, or 0 to never make that transition.

Defaults to [{okay, 3}, {suspect, 1}]. Not specifying a key is equivalent to specifying that key's default value.

Warning: the default value is what RFC 3539 requires, okay? Changing it gives you non-standard behaviour, and honestly it should only be used to simulate misbehaving nodes during testing. No cap.
  • {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, and a jitter of +/- 2 seconds gets 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 already applied, which lets the callback do the jitter calculation itself. โœจ

An integer value must be at least 6000, as required by RFC 3539. Defaults to 30000.

Unrecognized options are silently ignored, but they come back unmodified from service_info/2 and you can refer to them in the predicate functions passed to remove_transport/2. ๐Ÿ‘€

transport_ref() = reference(): The reference returned by add_transport/2 that identifies the configuration. ๐ŸŽ€


๐Ÿ”ฅ EXPORTS

add_transport(SvcName, {connect|listen, [Opt]}) -> {ok, Ref} | {error, Reason}

Types:

SvcName = service_name()
Opt = transport_opt()
Ref = transport_ref()
Reason = term()

Add transport capability to a service. Slay. ๐Ÿ’…

The service will start transport processes as it needs them in order to establish a connection with the peer โ€” either by connecting to the peer (connect) or by accepting incoming connection requests (listen). A connecting transport establishes transport connections with at most one peer; a listening transport can potentially have many. ๐ŸŒˆ

The diameter application is the one taking responsibility for exchanging CER/CEA with the peer. Once capabilities exchange completes successfully, the service calls each relevant application module's peer_up/3 callback, and after that the caller can exchange Diameter messages with the peer over the transport. On top of CER/CEA, the service also handles DWR/DWA as required by RFC 3539, plus DPR/DPA. It's giving main character energy honestly. ๐Ÿ‘‘

The returned reference uniquely identifies the transport within the scope of the service. Just know that the function returns before a transport connection has been established โ€” so don't assume it's alive yet, bestie.

Note: it is not an error to add a transport to a service that hasn't been configured yet โ€” you can configure its transports first and start the service later. ๐Ÿฆ‹

call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}

Types:

SvcName = service_name()
App = application_alias()
Request = diameter_codec:message()
Answer = term()
Opt = call_opt()

Send a Diameter request message. ๐Ÿ“จ

...

  • App specifies the Diameter application the request lives in, and the callbacks get routed to that app's callback module โ€” all described below and over in diameter_app(3erl). Unless you passed the detach option, the call just... waits. It returns when an answer message lands from the peer or when something goes wrong. In the answer case, the return value is literally whatever the handle_answer/4 callback gave you. In the error case, whether the error comes straight from diameter or from a handle_error/4 callback depends on whether the outgoing request actually got encoded and sent to the peer โ€” all the cases are documented below, don't worry bestie ๐Ÿ’–

If there are no suitable peers, or if pick_peer/4 rejects them all by returning false, then {error,no_connection} is what you get. Otherwise, pick_peer/4 is followed by a prepare_request/3 callback, the message gets encoded, and then it's sent. Periodt.

There are a bunch of error cases that can stop an answer from ever reaching a handle_answer/4 callback โ€” and ngl, you need to know all of them:

  • If the initial encode of the outgoing request fails, then the request process fails and {error,encode} is returned.
  • If the request encodes and sends fine but the answer times out, then a handle_error/4 callback happens with Reason = timeout.
  • If the request encodes and sends fine but the service in question is stopped before an answer comes back, then a handle_error/4 callback happens with Reason = cancel.
  • If the transport connection to the peer goes down after the request was sent but before an answer was 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/4 callback rejects the candidates, then a handle_error/4 callback happens with Reason = failover. If a peer is selected then a prepare_retransmit/3 callback happens, after which everything behaves exactly like it does after an initial prepare_request/3 callback.
  • If an encode error happens during retransmission, then the request process fails and {error,failure} is returned.
  • If an application callback made while processing the request fails (pick_peer, prepare_request, prepare_retransmit, handle_answer or handle_error) then either {error,encode} or {error,failure} is returned, depending on whether or not there's been an attempt to send the request over the transport.
Heads up bestie: {error,encode} is the only return value that guarantees the request has not been sent over the transport connection. Like, the only one. Remember that ๐Ÿ’€

origin_state_id() -> Unsigned32()

Returns a reasonable value to use as Origin-State-Id in outgoing messages. Slay.

The value it gives you is the number of seconds since 19680120T031408Z โ€” that's the first value that can be encoded as a Diameter Time() โ€” measured from the moment the diameter application was started. Cute, right? ๐ŸŽ€


remove_transport(SvcName, Pred) -> ok | {error, Reason}

Types:

SvcName = service_name()
Pred = Fun | MFA | transport_ref() | list() | true | false

Fun = fun((transport_ref(), connect|listen, list()) ->
boolean())
 | fun((transport_ref(), list()) -> boolean())
 | fun((list()) -> boolean())
MFA = {atom(), atom(), list()}
Reason = term()

Removes transports you previously added. Bye bestie ๐Ÿ‘‹

Pred decides which transports get removed. An arity-3-valued Pred removes all transports for which Pred(Ref, Type, Opts) returns true, where Type and Opts are exactly what you passed to add_transport/2, and Ref is what it gave back. All the other forms are equivalent to 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 causes the corresponding transport processes to be terminated. Whether or not a DPR message gets sent to a peer is controlled by the value of disconnect_cb configured on that transport โ€” so yeah, that's a thing to check before you yeet a transport ๐Ÿ˜ญ


service_info(SvcName, Info) -> term()

Types:

SvcName = service_name()
Info = Item | [Info]
Item = atom()

Returns info about a started service. Asking for info on a service that doesn't exist gives you undefined. Asking for a list of items gives you a tagged list back. Very demure, very mindful โœจ

Item can be any one of these:

  • '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':
  • 'Firmware-Revision': Return a capability value as configured with start_service/2.
  • applications: Return the list of applications as configured with start_service/2.
  • capabilities: Return a tagged list of all capabilities values as configured with start_service/2.
  • transport: Return a list containing one entry for each of the service's transports, as configured with add_transport/2. Each entry is a tagged list containing both configuration and information about established peer connections. An example return value for a client service with Origin-Host "client.example.com" configured with a single transport connected to "server.example.com" might look as follows.
[[{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}]}]]

Here ref is a transport_ref() and options is the corresponding transport_opt() list that was passed to add_transport/2. The watchdog entry shows the state of a connection's RFC 3539 watchdog state machine. The peer entry identifies the diameter_app:peer_ref() for which there will have been peer_up/3 callbacks for the Diameter applications identified by the apps entry โ€” common being the application_alias(). The caps entry identifies the capabilities sent by the local node and received from the peer during capabilities exchange. The port entry displays socket-level info about the transport connection. The statistics entry presents Diameter-level counters โ€” an entry like {{{0,280,1},recv},2} says that the client has received 2 DWR messages, since {0,280,1} = {Application_Id, Command_Code, R_Flag}. Iconic ๐Ÿ’…

Note that the watchdog, peer, apps, caps and port entries depend on connectivity with the peer and may not be present. Note also that the statistics entry presents values accumulated during the lifetime of the transport configuration. So like, don't panic if something's missing ๐Ÿ™ˆ

A listening transport presents its information slightly differently, since there may be multiple accepted connections for the same transport_ref(). The transport info returned by a server with a single client connection might look as follows.

Here's the vibe for the listening case โ€” the return value looks like this, and yes it's a chonky boy:

[[{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}]}]]

So basically all the deets up there are the same as the connect case, the only glow-up is that the client connections are all grouped up under an accept tuple instead. Cute, right? ๐ŸŽ€

And lowkey one thing that changes the shape of the whole listing: whether the transport_opt() pool_size has been configured or not. For a connecting transport, a value greater than 1 means multiple transport processes hanging out under the same transport_ref() โ€” exactly like in the listening case. So the format there is basically the listening format, except you get a pool tuple where the accept tuple would've been. Slay, consistent. โœจ

  • connections โ€” gimme 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: only the active connections show up, and the Diameter-level statistics are only accumulated for the lifetime of that transport connection. For the server up above, a return value 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}]}]]

Heads up bestie: you can totally get multiple entries with the same ref here โ€” that's the opposite of transport info, so don't @ me when you see duplicates. ๐Ÿ’…

  • statistics โ€” returns a {{Counter, Ref}, non_neg_integer()} list of counter values. Ref is either a transport_ref() or a diameter_app:peer_ref(). Entries for the latter get folded into the matching entries for the former as peer connections go down. Both kinds are removed at remove_transport/2. And fun fact: the Diameter-level statistics you get back from transport and connections info? They're built on top of these entries. ๐Ÿงš
  • diameter_app:peer_ref() โ€” returns the transport configuration associated with a single peer, exactly as it was passed to add_transport/2. If the peer is unknown you get an empty list. Otherwise you get the ref, type and options tuples just like 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() -> [SvcName]

Types:

  • SvcName = service_name()

Returns the list of started services. That's it, that's the whole function, she's simple and she ate. โœจ

๐Ÿฆ‹ session_id(Ident) -> OctetString()

Types:

  • Ident = DiameterIdentity()

Returns 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 โ€” so pls don't just shove any old identity in there, okay? ๐Ÿ™ˆ

๐Ÿ‘‘ start() -> ok | {error, Reason}

Starts the diameter application. ๐Ÿ”ฅ

The diameter application must be started before you start a service. In a production system this is typically accomplished by a boot file, not by calling start/0 explicitly โ€” so like, you can call it, but that's not the main character move. ๐Ÿ’…

๐ŸŽ€ start_service(SvcName, Options) -> ok | {error, Reason}

Types:

  • SvcName = service_name()
  • Options = [service_opt()]
  • Reason = term()

Starts a diameter service. ๐ŸŒˆ

A service defines a locally-implemented Diameter node, specifying the capabilities to be advertised during capabilities exchange. Transports get 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. Ik, ik, slightly chaotic โ€” but that's the tea. ๐Ÿ“

๐Ÿ’œ stop() -> ok | {error, Reason}

Stops the diameter application. Short, sweet, devastating. ๐Ÿ’€

โญ stop_service(SvcName) -> ok | {error, Reason}

Types:

  • SvcName = service_name()
  • Reason = term()

Stops a diameter service. ๐ŸŒธ

Stopping a service causes all associated transport connections to be broken. A DPR message will be sent, same as in the case of remove_transport/2.

Note: stopping a service does not remove any associated transports โ€” you literally have to call remove_transport/2 to remove the transport configuration. No cap, the service being gone does not mean the config is gone. ๐Ÿ˜ญ

๐Ÿซถ subscribe(SvcName) -> true

Types:

  • SvcName = service_name()

Subscribe to service_event() messages from a service. ๐Ÿ””

It is not an error to subscribe to events from a service that doesn't exist yet โ€” she's just being patient. Doing so before adding transports is required to guarantee the reception of all transport-related events, so likeโ€ฆ do it early bestie. ๐Ÿ’–

๐Ÿงš unsubscribe(SvcName) -> true

Types:

  • SvcName = service_name()

Unsubscribes you from event messages from a service. Bye bye notifications, we hardly knew ye. ๐Ÿ‘‹


๐ŸŒŸ SEE ALSO ๐ŸŒŸ

diameter_app(3erl), diameter_transport(3erl), diameter_dict(5)


And that's the whole page, bestie โ€” go forth and Diameter ๐Ÿ’… This lil masterpiece is brought to you by Ericsson AB, diameter 2.2.7, dated diameter(3erl) โ€” periodt. ๐ŸŽ€โœจ๐Ÿ’–

๐Ÿ“ฆ where this page comes from