From deed57ed8da08e3262d61197da2ed00391b94be6 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 19 Mar 2015 23:57:43 +0100 Subject: Improve language consistency in diameter(1) Akin to commit 85d44b58. --- lib/diameter/doc/src/diameter.xml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 638c1c4c2b..a5a99f7835 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -303,7 +303,7 @@ Defaults to none.

{timeout, &dict_Unsigned32;}

-The number of milliseconds after which the request should +Number of milliseconds after which the request should timeout. Defaults to 5000.

@@ -742,7 +742,7 @@ info fields of forms other than the above.

service_name() = term()

-The name of a service as passed to &start_service; and with which the +Name of a service as passed to &start_service; and with which the service is identified. There can be at most one service with a given name on a given node. Note that &make_ref; @@ -754,7 +754,7 @@ can be used to generate a service name that is somewhat unique.

service_opt()

-An option passed to &start_service;. +Option passed to &start_service;. Can be any &capability; as well as the following.

@@ -762,7 +762,7 @@ Can be any &capability; as well as the following.

{application, [&application_opt;]}

-Defines a Diameter application supported by the service.

+A Diameter application supported by the service.

A service must configure one tuple for each Diameter @@ -790,7 +790,7 @@ be matched by corresponding &capability; configuration, of | evaluable()}

-Specifies the degree to which the service allows multiple transport +The degree to which the service allows multiple transport connections to the same peer, as identified by its Origin-Host at capabilities exchange.

@@ -816,7 +816,7 @@ Defaults to nodes.

{sequence, {H,N} | &evaluable;}

-Specifies a constant value H for the topmost 32-N bits of +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;. @@ -851,7 +851,7 @@ outgoing requests.

{share_peers, boolean() | [node()] | evaluable()}

-Specifies nodes to which peer connections established on the local +Nodes to which peer connections established on the local Erlang node are communicated. Shared peers become available in the remote candidates list passed to &app_pick_peer; callbacks on remote nodes whose services are @@ -890,7 +890,7 @@ of a single Diameter node across multiple Erlang nodes.

{spawn_opt, [term()]}

-An options list passed to &spawn_opt; when spawning a process for an +Options list passed to &spawn_opt; when spawning a process for an incoming Diameter request, unless the transport in question specifies another value. Options monitor and link are ignored.

@@ -902,7 +902,7 @@ Defaults to the empty list.

{use_shared_peers, boolean() | [node()] | evaluable()}

-Specifies nodes from which communicated peers are made available in +Nodes from which communicated peers are made available in the remote candidates list of &app_pick_peer; callbacks.

@@ -942,7 +942,7 @@ each node from which requests are sent.

transport_opt()

-An option passed to &add_transport;. +Option passed to &add_transport;. Has one of the following types.

@@ -950,8 +950,7 @@ Has one of the following types.

{applications, [&application_alias;]}

-The list of Diameter applications to which the transport should be -restricted. +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.

@@ -984,7 +983,7 @@ TLS is desired over TCP as implemented by &man_tcp;.

{capabilities_cb, &evaluable;}

-A callback invoked upon reception of CER/CEA during capabilities +Callback invoked upon reception of CER/CEA during capabilities exchange in order to ask whether or not the connection should be accepted. Applied to the &transport_ref; and @@ -1207,7 +1206,7 @@ the same peer.

{spawn_opt, [term()]}

-Options list passed to &spawn_opt; when spawning a process for an +Options passed to &spawn_opt; when spawning a process for an incoming Diameter request. Options monitor and link are ignored.

-- cgit v1.2.3 From 75ee72b4e1d288b1d96194d0e352eb0c73db4a6f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 12 Feb 2015 11:18:52 +0100 Subject: Strip potentially large terms when sending outgoing Diameter messages Both incoming and outgoing Diameter messages pass through two or three processes, depending on whether they're incoming or outgoing: the transport process and corresponding peer_fsm process and (for incoming) watchdog processes. Since terms other than binary are copied when passing process boundaries, large terms lead to copying that can be problematic, if frequent enough. Since only the bin and transport_data fields of a diameter_packet record are needed by the transport process, discard others when sending outgoing messages. Strictly speaking, the statement that only the aforementioned fields are needed by the transport process depends on the transport process. It's true of those implemented by diameter (in diameter_tcp and diameter_sctp), but an implementation that makes use of other fields is assuming more than the documentation in diameter_transport(3) promises. --- lib/diameter/src/base/diameter_peer.erl | 21 +++++++++++++++------ lib/diameter/src/base/diameter_traffic.erl | 12 +++++++++--- lib/diameter/src/base/diameter_watchdog.erl | 11 +++++++++-- 3 files changed, 33 insertions(+), 11 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl index ea326dd03e..89b63c8a92 100644 --- a/lib/diameter/src/base/diameter_peer.erl +++ b/lib/diameter/src/base/diameter_peer.erl @@ -232,12 +232,22 @@ recv(Pid, Pkt) -> %% # send/2 %% --------------------------------------------------------------------------- -send(Pid, #diameter_packet{transport_data = undefined, - bin = Bin}) -> - send(Pid, Bin); +send(Pid, Msg) -> + ifc_send(Pid, {send, strip(Msg)}). -send(Pid, Pkt) -> - ifc_send(Pid, {send, Pkt}). +%% Send only binary when possible. +strip(#diameter_packet{transport_data = undefined, + bin = Bin}) -> + Bin; + +%% Strip potentially large message terms. +strip(#diameter_packet{transport_data = T, + bin = Bin}) -> + #diameter_packet{transport_data = T, + bin = Bin}; + +strip(Msg) -> + Msg. %% --------------------------------------------------------------------------- %% # close/1 @@ -326,7 +336,6 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %% --------------------------------------------------------- -%% INTERNAL FUNCTIONS %% --------------------------------------------------------- %% ifc_send/2 diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 0b503338a6..18c1965f77 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -1679,8 +1679,14 @@ recv(TPid, Pid, TRef, Ref) -> %% send/2 -send(Pid, Pkt) -> - Pid ! {send, Pkt}. +send(Pid, Pkt) -> %% Strip potentially large message terms. + #diameter_packet{header = H, + bin = Bin, + transport_data = T} + = Pkt, + Pid ! {send, #diameter_packet{header = H, + bin = Bin, + transport_data = T}}. %% retransmit/4 diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 67715906e8..8223b7df98 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -572,11 +572,18 @@ rcv('DWR', Pkt, #watchdog{transport = TPid, DPkt = diameter_codec:decode(Dict0, Pkt), diameter_traffic:incr(recv, DPkt, TPid, Dict0), diameter_traffic:incr_error(recv, DPkt, TPid, Dict0), - EPkt = encode(dwa, Dict0, Pkt), + #diameter_packet{header = H, + transport_data = T, + bin = Bin} + = EPkt + = encode(dwa, Dict0, Pkt), diameter_traffic:incr(send, EPkt, TPid, Dict0), diameter_traffic:incr_rc(send, EPkt, TPid, Dict0), - send(TPid, {send, EPkt}), + %% Strip potentially large message terms. + send(TPid, {send, #diameter_packet{header = H, + transport_data = T, + bin = Bin}}), ?LOG(send, 'DWA'); rcv('DWA', Pkt, #watchdog{transport = TPid, -- cgit v1.2.3 From 1590920c910c030369fbf871b63f6836b988e90a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 12 Feb 2015 11:41:59 +0100 Subject: Add service_opt() string_decode To control whether stringish Diameter types are decoded to string or left as binary. The motivation is the same as in the parent commit: to avoid large strings being copied when incoming Diameter messages are passed between processes; or *if* in the case of messages destined for handle_request and handle_answer callbacks, since these are decoded in the dedicated processes that the callbacks take place in. It would be possible to do something about other messages without requiring an option, but disabling the decode is the most effective. The value is a boolean(), true being the default for backwards compatibility. Setting false causes both diameter_caps records and decoded messages to contain binary() in relevant places that previously had string(): diameter_app(3) callbacks need to be prepared for the change. The Diameter types affected are OctetString and the derived types that can contain arbitrarily large values: OctetString, UTF8String, DiameterIdentity, DiameterURI, IPFilterRule, and QoSFilterRule. Time and Address are unaffected. The DiameterURI decode has been redone using re(3), which both simplifies and does away with a vulnerability resulting from the conversion of arbitrary strings to atom. The solution continues the use and abuse of the process dictionary for encode/decode purposes, last seen in commit 0f9cdba. --- lib/diameter/doc/src/diameter.xml | 24 +++++ lib/diameter/doc/src/diameter_dict.xml | 9 +- lib/diameter/doc/src/seealso.ent | 5 ++ lib/diameter/src/base/diameter.erl | 3 +- lib/diameter/src/base/diameter_capx.erl | 40 +++++++-- lib/diameter/src/base/diameter_codec.erl | 39 ++++++++- lib/diameter/src/base/diameter_config.erl | 15 +++- lib/diameter/src/base/diameter_peer_fsm.erl | 16 +++- lib/diameter/src/base/diameter_service.erl | 52 +++++++---- lib/diameter/src/base/diameter_traffic.erl | 57 ++++++++---- lib/diameter/src/base/diameter_types.erl | 130 ++++++++++++++-------------- lib/diameter/src/base/diameter_watchdog.erl | 40 +++++---- lib/diameter/test/diameter_config_SUITE.erl | 3 + 13 files changed, 303 insertions(+), 130 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index a5a99f7835..cb397614e5 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -899,6 +899,30 @@ Options monitor and link are ignored.

Defaults to the empty list.

+ +{string_decode, boolean()} + +

+Whether or not to decode AVPs of type &dict_OctetString; and its +derived types &dict_DiameterIdentity;, &dict_DiameterURI;, +&dict_IPFilterRule;, &dict_QoSFilterRule;, and &dict_UTF8String;. +If true then AVPs of these types are decoded to string(). +If false then values are retained as binary().

+ +

+Defaults to true.

+ + +

+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.

+
+ +
+ {use_shared_peers, boolean() | [node()] | evaluable()}

diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 810a146b88..9db9bcffde 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -528,6 +528,11 @@ in a request record when sending a request, returned in a resulting answer record and passed to a &app_handle_request; callback upon reception of an incoming request.

+

+In cases in which there is a choice between list() and binary() types +for OctetString() and derived types, the representation is determined +by the value of &mod_string_decode;.

+

Basic AVP Data Formats

@@ -541,7 +546,7 @@ callback upon reception of an incoming request.

-OctetString() = [0..255]
+OctetString() = string() | binary()
 Integer32()   = -2147483647..2147483647
 Integer64()   = -9223372036854775807..9223372036854775807
 Unsigned32()  = 0..4294967295
@@ -603,7 +608,7 @@ and {{2104,2,26},{9,42,23}} (both inclusive) can be encoded.

-UTF8String() = [integer()]
+UTF8String() = [integer()] | binary()
 

diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent index 44541afb9b..b0e3a2c712 100644 --- a/lib/diameter/doc/src/seealso.ent +++ b/lib/diameter/doc/src/seealso.ent @@ -69,6 +69,8 @@ significant. connect_timer'> watchdog_timer'> +diameter:service_opt() string_decode'> + handle_answer/4'> @@ -102,6 +104,9 @@ significant. Address()'> DiameterIdentity()'> +DiameterURI()'> +IPFilterRule()'> +QoSFilterRule()'> Grouped()'> OctetString()'> Time()'> diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 1bbdf6e34d..a3c259c651 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2014. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -306,6 +306,7 @@ call(SvcName, App, Message) -> | {restrict_connections, restriction()} | {sequence, sequence() | evaluable()} | {share_peers, remotes()} + | {string_decode, boolean()} | {use_shared_peers, remotes()} | {spawn_opt, list()}. diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 93548ecafd..7dc61f229f 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -50,7 +50,8 @@ -export([build_CER/2, recv_CER/3, recv_CEA/3, - make_caps/2]). + make_caps/2, + binary_caps/1]). -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). @@ -115,7 +116,8 @@ mk_caps(Caps0, Opts) -> -define(SC(K,F), set_cap({K, Val}, {Caps, #diameter_caps{F = false} = C}) -> - {Caps#diameter_caps{F = cap(K, Val)}, C#diameter_caps{F = true}}). + {Caps#diameter_caps{F = cap(K, copy(Val))}, + C#diameter_caps{F = true}}). ?SC('Origin-Host', origin_host); ?SC('Origin-Realm', origin_realm); @@ -375,10 +377,10 @@ capx_to_caps(CEX, Dict) -> 'Firmware-Revision', 'AVP'], CEX), - #diameter_caps{origin_host = OH, - origin_realm = OR, + #diameter_caps{origin_host = copy(OH), + origin_realm = copy(OR), vendor_id = VId, - product_name = PN, + product_name = copy(PN), origin_state_id = OSI, host_ip_address = IP, supported_vendor_id = SV, @@ -389,6 +391,32 @@ capx_to_caps(CEX, Dict) -> firmware_revision = FR, avp = X}. +%% Copy binaries to avoid retaining a reference to a large binary +%% containing AVPs we aren't interested in. +copy(B) + when is_binary(B) -> + binary:copy(B); + +copy(T) -> + T. + +%% binary_caps/1 +%% +%% Encode stringish capabilities with {string_decode, false}. + +binary_caps(Caps) -> + lists:foldl(fun bcaps/2, Caps, [#diameter_caps.origin_host, + #diameter_caps.origin_realm, + #diameter_caps.product_name]). + +bcaps(N, Caps) -> + case element(N, Caps) of + undefined -> + Caps; + V -> + setelement(N, Caps, iolist_to_binary(V)) + end. + %% --------------------------------------------------------------------------- %% --------------------------------------------------------------------------- diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index b4ecb63961..2de0dcf373 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2014. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,6 +22,8 @@ -export([encode/2, decode/2, decode/3, + setopts/1, + getopt/1, collect_avps/1, decode_header/1, sequence_numbers/1, @@ -58,6 +60,41 @@ %% | AVPs ... %% +-+-+-+-+-+-+-+-+-+-+-+-+- +%%% --------------------------------------------------------------------------- +%%% # setopts/1 +%%% # getopt/1 +%%% --------------------------------------------------------------------------- + +%% These functions are a compromise in the same vein as the use of the +%% process dictionary in diameter_gen.hrl in generated codec modules. +%% Instead of rewriting the entire dictionary generation to pass +%% encode/decode options around, the calling process sets them by +%% calling setopts/1. At current, the only option is whether or not to +%% decode binaries as strings, which is used by diameter_types. + +setopts(Opts) + when is_list(Opts) -> + lists:foreach(fun setopt/1, Opts). + +%% Decode stringish types to string()? The default true is for +%% backwards compatibility. +setopt({string_decode = K, false = B}) -> + setopt(K, B); + +setopt(_) -> + ok. + +setopt(Key, Value) -> + put({diameter, Key}, Value). + +getopt(Key) -> + case get({diameter, Key}) of + undefined when Key == string_decode -> + true; + V -> + V + end. + %%% --------------------------------------------------------------------------- %%% # encode/2 %%% --------------------------------------------------------------------------- diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index c0a4f7df69..e446f7c479 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -642,13 +642,23 @@ make_config(SvcName, Opts) -> {false, monitor}, {?NOMASK, sequence}, {nodes, restrict_connections}, + {true, string_decode}, {[], spawn_opt}]), + D = proplists:get_value(string_decode, SvcOpts, true), + #service{name = SvcName, rec = #diameter_service{applications = Apps, - capabilities = Caps}, + capabilities = binary_caps(Caps, D)}, options = SvcOpts}. +binary_caps(Caps, true) -> + Caps; +binary_caps(Caps, false) -> + diameter_capx:binary_caps(Caps). + +%% make_opts/2 + make_opts(Opts, Defs) -> Known = [{K, get_opt(K, Opts, D)} || {D,K} <- Defs], Unknown = Opts -- Known, @@ -667,7 +677,8 @@ opt(K, false = B) opt(K, true = B) when K == share_peers; - K == use_shared_peers -> + K == use_shared_peers; + K == string_decode -> B; opt(restrict_connections, T) diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index ee6e7dd89e..cbf601f6f5 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2014. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -138,7 +138,8 @@ %% # start/3 %% --------------------------------------------------------------------------- --spec start(T, [Opt], {diameter:sequence(), +-spec start(T, [Opt], {[diameter:service_opt()] + | diameter:sequence(), %% from old code [node()], module(), #diameter_service{}}) @@ -177,10 +178,15 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({Ack, WPid, {M, Ref} = T, Opts, {Mask, Nodes, Dict0, Svc}}) -> +i({Ack, WPid, T, Opts, {{_,_} = Mask, Nodes, Dict0, Svc}}) -> %% from old code + i({Ack, WPid, T, Opts, {[{sequence, Mask}], Nodes, Dict0, Svc}}); + +i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) -> erlang:monitor(process, WPid), wait(Ack, WPid), diameter_stats:reg(Ref), + diameter_codec:setopts(SvcOpts), + {_,_} = Mask = proplists:get_value(sequence, SvcOpts), {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]), putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}), putr(?DPR_KEY, [F || {_, F} <- Ds]), @@ -699,6 +705,8 @@ build_answer('CER', = Pkt, #state{dictionary = Dict0} = S) -> + diameter_codec:setopts([{string_decode, false}]), + {SupportedApps, RCaps, CEA} = recv_CER(CER, S), [RC, IS] = Dict0:'#get-'(['Result-Code', 'Inband-Security-Id'], CEA), @@ -886,6 +894,8 @@ handle_CEA(#diameter_packet{header = H} = DPkt = diameter_codec:decode(Dict0, Pkt), + diameter_codec:setopts([{string_decode, false}]), + RC = result_code(incr_rc(recv, DPkt, Dict0)), {SApps, IS, RCaps} = recv_CEA(DPkt, S), diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 04401a3d87..a01bcdd4e7 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -130,7 +130,8 @@ :: [{sequence, diameter:sequence()} %% sequence mask | {share_peers, diameter:remotes()} %% broadcast to | {use_shared_peers, diameter:remotes()} %% use from - | {restrict_connections, diameter:restriction()}]}). + | {restrict_connections, diameter:restriction()} + | {string_decode, boolean()}]}). %% shared_peers reflects the peers broadcast from remote nodes. %% Record representing an RFC 3539 watchdog process implemented by @@ -261,16 +262,22 @@ whois(SvcName) -> %% --------------------------------------------------------------------------- -spec pick_peer(SvcName, AppOrAlias, Opts) - -> {{TPid, Caps, App}, Mask} - | false - | {error, term()} + -> {{TPid, Caps, App}, Mask, SvcOpts} + | false %% no selection + | {error, no_service} when SvcName :: diameter:service_name(), - AppOrAlias :: {alias, diameter:app_alias()} | #diameter_app{}, - Opts :: tuple(), + AppOrAlias :: #diameter_app{} + | {alias, diameter:app_alias()}, + Opts :: {fun((Dict :: module()) -> [term()]), + diameter:peer_filter(), + Xtra :: list()}, TPid :: pid(), Caps :: #diameter_caps{}, App :: #diameter_app{}, - Mask :: diameter:sequence(). + Mask :: diameter:sequence(), + SvcOpts :: [diameter:service_opt()]. +%% Extract Mask in the returned tuple so that diameter_traffic doesn't +%% need to know about the ordering of SvcOpts used here. pick_peer(SvcName, App, Opts) -> pick(lookup_state(SvcName), App, Opts). @@ -287,10 +294,10 @@ pick(#state{service = #diameter_service{applications = Apps}} Opts) -> %% initial call from diameter:call/4 pick(S, find_outgoing_app(Alias, Apps), Opts); -pick(_, false, _) -> - false; +pick(_, false = No, _) -> + No; -pick(#state{options = [{_, Mask} | _]} +pick(#state{options = [{_, Mask} | SvcOpts]} = S, #diameter_app{module = ModX, dictionary = Dict} = App0, @@ -299,7 +306,7 @@ pick(#state{options = [{_, Mask} | _]} [_,_] = RealmAndHost = diameter_lib:eval([DestF, Dict]), case pick_peer(App, RealmAndHost, Filter, S) of {TPid, Caps} -> - {{TPid, Caps, App}, Mask}; + {{TPid, Caps, App}, Mask, SvcOpts}; false = No -> No end. @@ -690,7 +697,8 @@ service_options(Opts) -> {restrict_connections, proplists:get_value(restrict_connections, Opts, ?RESTRICT)}, - {spawn_opt, proplists:get_value(spawn_opt, Opts, [])}]. + {spawn_opt, proplists:get_value(spawn_opt, Opts, [])}, + {string_decode, proplists:get_value(string_decode, Opts, true)}]. %% The order of options is significant since we match against the list. mref(false = No) -> @@ -802,10 +810,13 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT, when Type == connect; Type == accept -> #diameter_service{applications = Apps} - = Svc + = Svc1 = merge_service(Opts, Svc0), - {_,_} = Mask = proplists:get_value(sequence, SvcOpts), - RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, Mask]), + Svc = binary_caps(Svc1, proplists:get_value(string_decode, SvcOpts, true)), + RecvData = diameter_traffic:make_recvdata([SvcName, + PeerT, + Apps, + SvcOpts]), T = {{spawn_opts([Opts, SvcOpts]), RecvData}, Opts, SvcOpts, Svc}, Rec = #watchdog{type = Type, ref = Ref, @@ -816,8 +827,13 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT, [], N). +binary_caps(Svc, true) -> + Svc; +binary_caps(#diameter_service{capabilities = Caps} = Svc, false) -> + Svc#diameter_service{capabilities = diameter_capx:binary_caps(Caps)}. + wd(Type, Ref, T, WatchdogT, Rec) -> - Pid = wd(Type, Ref, T), + Pid = start_watchdog(Type, Ref, T), insert(WatchdogT, Rec#watchdog{pid = Pid}), Pid. @@ -831,7 +847,7 @@ spawn_opts(Optss) -> T /= link, T /= monitor]. -wd(Type, Ref, T) -> +start_watchdog(Type, Ref, T) -> {_MRef, Pid} = diameter_watchdog:start({Type, Ref}, T), Pid. @@ -852,7 +868,7 @@ ms({applications, As}, #diameter_service{applications = Apps} = S) %% The fact that all capabilities can be configured on the transports %% means that the service doesn't necessarily represent a single -%% locally implemented Diameter peer as identified by Origin-Host: a +%% locally implemented Diameter node as identified by Origin-Host: a %% transport can configure its own Origin-Host. This means that the %% service little more than a placeholder for default capabilities %% plus a list of applications that individual transports can choose diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 18c1965f77..a9dc46ea31 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -77,7 +77,8 @@ {peerT :: ets:tid(), service_name :: diameter:service_name(), apps :: [#diameter_app{}], - sequence :: diameter:sequence()}). + sequence :: diameter:sequence(), + codec :: list()}). %% Record stored in diameter_request for each outgoing request. -record(request, @@ -92,11 +93,16 @@ %% # make_recvdata/1 %% --------------------------------------------------------------------------- -make_recvdata([SvcName, PeerT, Apps, Mask | _]) -> +make_recvdata([SvcName, PeerT, Apps, {_,_} = Mask | _]) -> %% from old code + make_recvdata([SvcName, PeerT, Apps, [{sequence, Mask}]]); + +make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) -> + {_,_} = Mask = proplists:get_value(sequence, SvcOpts), #recvdata{service_name = SvcName, peerT = PeerT, apps = Apps, - sequence = Mask}. + sequence = Mask, + codec = [T || {K,_} = T <- SvcOpts, K == string_decode]}. %% --------------------------------------------------------------------------- %% peer_up/1 @@ -270,8 +276,11 @@ recv_request(TPid, #diameter_packet{header = #diameter_header{application_id = Id}} = Pkt, Dict0, - #recvdata{peerT = PeerT, apps = Apps} + #recvdata{peerT = PeerT, + apps = Apps, + codec = Opts} = RecvData) -> + diameter_codec:setopts(Opts), send_A(recv_R(diameter_service:find_incoming_app(PeerT, TPid, Id, Apps), TPid, Pkt, @@ -279,7 +288,13 @@ recv_request(TPid, RecvData), TPid, Dict0, - RecvData). + RecvData); + +recv_request(TPid, Pkt, Dict0, RecvData) -> %% from old code + recv_request(TPid, + Pkt, + Dict0, + #recvdata{} = erlang:append_element(RecvData, [])). %% recv_R/5 @@ -1225,10 +1240,9 @@ answer_rc(_, _, Sent) -> send_R(SvcName, AppOrAlias, Msg, Opts, Caller) -> case pick_peer(SvcName, AppOrAlias, Msg, Opts) of - {{_,_,_} = Transport, Mask} -> + {Transport, Mask, SvcOpts} -> + diameter_codec:setopts(SvcOpts), send_request(Transport, Mask, Msg, Opts, Caller, SvcName); - false -> - {error, no_connection}; {error, _} = No -> No end. @@ -1290,6 +1304,8 @@ send_request({TPid, Caps, App} SvcName, []). +%% send_R/7 + send_R({send, Msg}, Pkt, Transport, Opts, Caller, SvcName, Fs) -> send_R(make_request_packet(Msg, Pkt), Transport, @@ -1550,7 +1566,9 @@ a(Hdr, SvcName, discard) -> %% timer value is ignored. This means that an answer could be accepted %% from a peer after timeout in the case of failover. -retransmit({{_,_,App} = Transport, _Mask}, Req, Opts, SvcName, Timeout) -> +%% retransmit/5 + +retransmit({{_,_,App} = Transport, _, _}, Req, Opts, SvcName, Timeout) -> try retransmit(Transport, Req, SvcName, Timeout) of T -> recv_A(Timeout, SvcName, App, Opts, T) catch @@ -1571,17 +1589,26 @@ pick_peer(SvcName, pick_peer(SvcName, App, Msg, Opts#options{extra = []}); pick_peer(_, _, undefined, _) -> - false; + {error, no_connection}; pick_peer(SvcName, AppOrAlias, Msg, #options{filter = Filter, extra = Xtra}) -> - diameter_service:pick_peer(SvcName, - AppOrAlias, - {fun(D) -> get_destination(D, Msg) end, - Filter, - Xtra}). + pick(diameter_service:pick_peer(SvcName, + AppOrAlias, + {fun(D) -> get_destination(D, Msg) end, + Filter, + Xtra})). + +pick({{_,_,_} = Transport, Mask}) -> %% from old code; dialyzer complains + {Transport, Mask, []}; %% about this + +pick(false) -> + {error, no_connection}; + +pick(T) -> + T. %% handle_error/4 diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl index 442d90c98b..28a0635c57 100644 --- a/lib/diameter/src/base/diameter_types.erl +++ b/lib/diameter/src/base/diameter_types.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -90,7 +90,12 @@ 'OctetString'(decode, Bin) when is_binary(Bin) -> - binary_to_list(Bin); + case diameter_codec:getopt(string_decode) of + true -> + binary_to_list(Bin); + _ -> + Bin + end; 'OctetString'(decode, B) -> ?INVALID_LENGTH(B); @@ -298,17 +303,19 @@ 'OctetString'(M, lists:duplicate(0,7)); 'DiameterURI'(encode, #diameter_uri{type = Type, - fqdn = D, - port = P, + fqdn = DN, + port = PN, transport = T, - protocol = Prot} - = U) -> - S = lists:append([atom_to_list(Type), "://", D, - ":", integer_to_list(P), + protocol = P}) + when (Type == 'aaa' orelse Type == 'aaas'), + is_integer(PN), + 0 =< PN, + (T == tcp orelse T == sctp orelse T == udp), + (P == diameter orelse P == radius orelse P == 'tacacs+') -> + iolist_to_binary([atom_to_list(Type), "://", DN, + ":", integer_to_list(PN), ";transport=", atom_to_list(T), - ";protocol=", atom_to_list(Prot)]), - U = scan_uri(S), %% assert - list_to_binary(S); + ";protocol=", atom_to_list(P)]); 'DiameterURI'(encode, Str) -> Bin = iolist_to_binary(Str), @@ -321,7 +328,6 @@ 'IPFilterRule'(encode = M, zero) -> 'OctetString'(M, lists:duplicate(0,33)); -%% TODO: parse grammar. 'IPFilterRule'(M, X) -> 'OctetString'(M, X). @@ -331,7 +337,6 @@ 'QoSFilterRule'(encode = M, zero = X) -> 'IPFilterRule'(M, X); -%% TODO: parse grammar. 'QoSFilterRule'(M, X) -> 'OctetString'(M, X). @@ -339,7 +344,13 @@ 'UTF8String'(decode, Bin) when is_binary(Bin) -> - tl([0|_] = unicode:characters_to_list([0, Bin])); %% assert list return + case diameter_codec:getopt(string_decode) of + true -> + %% assert list return + tl([0|_] = unicode:characters_to_list([0, Bin])); + false -> + <<_/binary>> = unicode:characters_to_binary(Bin) + end; 'UTF8String'(decode, B) -> ?INVALID_LENGTH(B); @@ -507,55 +518,42 @@ msb(false) -> ?TIME_2036. %% %% aaa-protocol = ( "diameter" / "radius" / "tacacs+" ) -scan_uri(Bin) - when is_binary(Bin) -> - scan_uri(binary_to_list(Bin)); -scan_uri("aaa://" ++ Rest) -> - scan_fqdn(Rest, #diameter_uri{type = aaa}); -scan_uri("aaas://" ++ Rest) -> - scan_fqdn(Rest, #diameter_uri{type = aaas}). - -scan_fqdn(S, U) -> - {[_|_] = F, Rest} = lists:splitwith(fun is_fqdn/1, S), - scan_opt_port(Rest, U#diameter_uri{fqdn = F}). - -scan_opt_port(":" ++ S, U) -> - {[_|_] = P, Rest} = lists:splitwith(fun is_digit/1, S), - scan_opt_transport(Rest, U#diameter_uri{port = list_to_integer(P)}); -scan_opt_port(S, U) -> - scan_opt_transport(S, U). - -scan_opt_transport(";transport=" ++ S, U) -> - {P, Rest} = transport(S), - scan_opt_protocol(Rest, U#diameter_uri{transport = P}); -scan_opt_transport(S, U) -> - scan_opt_protocol(S, U). - -scan_opt_protocol(";protocol=" ++ S, U) -> - {P, ""} = protocol(S), - U#diameter_uri{protocol = P}; -scan_opt_protocol("", U) -> - U. - -transport("tcp" ++ S) -> - {tcp, S}; -transport("sctp" ++ S) -> - {sctp, S}; -transport("udp" ++ S) -> - {udp, S}. - -protocol("diameter" ++ S) -> - {diameter, S}; -protocol("radius" ++ S) -> - {radius, S}; -protocol("tacacs+" ++ S) -> - {'tacacs+', S}. - -is_fqdn(C) -> - is_digit(C) orelse is_alpha(C) orelse C == $. orelse C == $-. - -is_alpha(C) -> - ($a =< C andalso C =< $z) orelse ($A =< C andalso C =< $Z). - -is_digit(C) -> - $0 =< C andalso C =< $9. +scan_uri(Bin) -> + RE = "^(aaas?)://" + "([-a-zA-Z0-9.]+)" + "(:([0-9]+))?" + "(;transport=(tcp|sctp|udp))?" + "(;protocol=(diameter|radius|tacacs\\+))?$", + {match, [A, DN, PN, T, P]} = re:run(Bin, + RE, + [{capture, [1,2,4,6,8], binary}]), + #diameter_uri{port = PN0, + transport = T0, + protocol = P0} + = #diameter_uri{}, + #diameter_uri{type = to_atom(A), + fqdn = from_bin(DN), + port = to_int(PN, PN0), + transport = to_atom(T, T0), + protocol = to_atom(P, P0)}. + +from_bin(B) -> + case diameter_codec:getopt(string_decode) of + true -> + binary_to_list(B); + false -> + B + end. + +to_int(<<>>, N) -> + N; +to_int(B, _) -> + binary_to_integer(B). + +to_atom(<<>>, A) -> + A; +to_atom(B, _) -> + to_atom(B). + +to_atom(B) -> + binary_to_atom(B, latin1). diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 8223b7df98..d14ddb758b 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -124,14 +124,15 @@ i({Ack, T, Pid, {RecvData, wait(Ack, Pid), {_, Seed} = diameter_lib:seed(), random:seed(Seed), - putr(restart, {T, Opts, Svc}), %% save seeing it in trace - putr(dwr, dwr(Caps)), %% + putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace + putr(dwr, dwr(Caps)), %% + diameter_codec:setopts([{string_decode, false}]), {_,_} = Mask = proplists:get_value(sequence, SvcOpts), Restrict = proplists:get_value(restrict_connections, SvcOpts), Nodes = restrict_nodes(Restrict), Dict0 = common_dictionary(Apps), #watchdog{parent = Pid, - transport = start(T, Opts, Mask, Nodes, Dict0, Svc), + transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc), tw = proplists:get_value(watchdog_timer, Opts, ?DEFAULT_TW_INIT), @@ -166,11 +167,11 @@ config({okay, N}, Rec) when ?IS_NATURAL(N) -> Rec#config{okay = N}. -%% start/5 +%% start/6 -start(T, Opts, Mask, Nodes, Dict0, Svc) -> +start(T, Opts, SvcOpts, Nodes, Dict0, Svc) -> {_MRef, Pid} - = diameter_peer_fsm:start(T, Opts, {Mask, Nodes, Dict0, Svc}), + = diameter_peer_fsm:start(T, Opts, {SvcOpts, Nodes, Dict0, Svc}), Pid. %% common_dictionary/1 @@ -320,7 +321,7 @@ code_change(_, State, _) -> %% expiry; or another watchdog is saying the same after reestablishing %% a connection previously had by this one. transition(close, #watchdog{}) -> - {{accept, _}, _, _} = getr(restart), %% assert + {accept, _} = role(), %% assert stop; %% Service is asking for the peer to be taken down gracefully. @@ -369,7 +370,7 @@ transition({open, TPid, Hosts, _} = Open, restrict = {_,R}, config = #config{suspect = OS}} = S) -> - case okay(getr(restart), Hosts, R) of + case okay(role(), Hosts, R) of okay -> set_watchdog(S#watchdog{status = okay, num_dwa = OS}); @@ -423,7 +424,7 @@ transition({'DOWN', _, process, TPid, _Reason} = D, = S0) -> S = S0#watchdog{pending = false, transport = undefined}, - {{M,_}, _, _} = getr(restart), + {M,_} = role(), %% Close an accepting watchdog immediately if there's no %% restriction on the number of connections to the same peer: the @@ -490,7 +491,7 @@ encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD} %% okay/3 -okay({{accept, Ref}, _, _}, Hosts, Restrict) -> +okay({accept, Ref}, Hosts, Restrict) -> T = {?MODULE, connection, Ref, Hosts}, diameter_reg:add(T), if Restrict -> @@ -501,7 +502,7 @@ okay({{accept, Ref}, _, _}, Hosts, Restrict) -> %% Register before matching so that at least one of two registering %% processes will match the other. -okay({{connect, _}, _, _}, _, _) -> +okay({connect, _}, _, _) -> okay. %% okay/2 @@ -516,6 +517,11 @@ okay(C) -> [_|_] = [send(P, close) || {_,P} <- C, self() /= P], reopen. +%% role/0 + +role() -> + element(1, getr(restart)). + %% set_watchdog/1 set_watchdog(#watchdog{tw = TwInit, @@ -801,26 +807,28 @@ restart(S) -> %% reconnect has won race with timeout %% state down rather then initial when receiving notification of an %% open connection. -restart({{connect, _} = T, Opts, Svc}, +restart({T, Opts, Svc}, S) -> %% put in old code + restart({T, Opts, Svc, []}, S); + +restart({{connect, _} = T, Opts, Svc, SvcOpts}, #watchdog{parent = Pid, - sequence = Mask, restrict = {R,_}, dictionary = Dict0} = S) -> send(Pid, {reconnect, self()}), Nodes = restrict_nodes(R), - S#watchdog{transport = start(T, Opts, Mask, Nodes, Dict0, Svc), + S#watchdog{transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc), restrict = {R, lists:member(node(), Nodes)}}; %% No restriction on the number of connections to the same peer: just %% die. Note that a state machine never enters state REOPEN in this %% case. -restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) -> +restart({{accept, _}, _, _, _}, #watchdog{restrict = {_, false}}) -> stop; %% 'DOWN' was in old code: 'close' was not sent %% Otherwise hang around until told to die, either by the service or %% by another watchdog. -restart({{accept, _}, _, _}, S) -> +restart({{accept, _}, _, _, _}, S) -> S. %% Don't currently use Opts/Svc in the accept case. diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl index ad5b3f9420..642fe2adb4 100644 --- a/lib/diameter/test/diameter_config_SUITE.erl +++ b/lib/diameter/test/diameter_config_SUITE.erl @@ -82,6 +82,9 @@ [false], [[node(), node()]]], [[x]]}, + {string_decode, + [[true], [false]], + [[0], [x]]}, {invalid_option, %% invalid service options are rejected [], [[x], -- cgit v1.2.3 From da83265894153a9cee7c7627441c99084ad97f85 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 16 Mar 2015 12:26:38 +0100 Subject: Test {string_decode, false} in traffic suite By adding string decode or not in the server or client as another combination. Run all traffic cases in parallel: remove the sequential tests. Common test seems unable to deal with {group, X, [parallel]} within a group. --- lib/diameter/test/diameter_traffic_SUITE.erl | 195 +++++++++++++++++++-------- 1 file changed, 136 insertions(+), 59 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 9822b95301..10c58ab6e7 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -122,8 +122,6 @@ -define(ADDR, {127,0,0,1}). --define(CLIENT, "CLIENT"). --define(SERVER, "SERVER"). -define(REALM, "erlang.org"). -define(HOST(Host, Realm), Host ++ [$.|Realm]). @@ -141,11 +139,19 @@ %% Which common dictionary to use in the clients. -define(RFCS, [rfc3588, rfc6733]). +%% Whether to decode stringish Diameter types to strings, or leave +%% them as binary. +-define(STRING_DECODES, [true, false]). + -record(group, - {client_encoding, + {client_service, + client_encoding, client_dict0, + client_strings, + server_service, server_encoding, - server_container}). + server_container, + server_strings}). %% Not really what we should be setting unless the message is sent in %% the common application but diameter doesn't care. @@ -166,7 +172,7 @@ ?answer_message(_, ResultCode)). %% Config for diameter:start_service/2. --define(SERVICE(Name), +-define(SERVICE(Name, Decode), [{'Origin-Host', Name ++ "." ++ ?REALM}, {'Origin-Realm', ?REALM}, {'Host-IP-Address', [?ADDR]}, @@ -175,6 +181,7 @@ {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, {'Acct-Application-Id', [?DIAMETER_APP_ID_ACCOUNTING]}, {restrict_connections, false}, + {string_decode, Decode}, {spawn_opt, [{min_heap_size, 5000}]} | [{application, [{dictionary, D}, {module, ?MODULE}, @@ -227,28 +234,53 @@ suite() -> [{timetrap, {seconds, 60}}]. all() -> - [start, start_services, add_transports, result_codes] - ++ [{group, ?util:name([R,D,A,C]), P} || R <- ?ENCODINGS, - D <- ?RFCS, - A <- ?ENCODINGS, - C <- ?CONTAINERS, - P <- [[], [parallel]]] - ++ [outstanding, remove_transports, empty, stop_services, stop]. + [start, result_codes, {group, traffic}, outstanding, empty, stop]. groups() -> Ts = tc(), - [{?util:name([R,D,A,C]), [], Ts} || R <- ?ENCODINGS, - D <- ?RFCS, - A <- ?ENCODINGS, - C <- ?CONTAINERS]. + [{?util:name([R,D,A,C]), [parallel], Ts} || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS] + ++ + [{?util:name([R,D,A,C,SD,CD]), + [], + [start_services, + add_transports, + result_codes, + {group, ?util:name([R,D,A,C])}, + remove_transports, + stop_services]} + || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS, + SD <- ?STRING_DECODES, + CD <- ?STRING_DECODES] + ++ + [{traffic, [parallel], [{group, ?util:name([R,D,A,C,SD,CD])} + || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS, + SD <- ?STRING_DECODES, + CD <- ?STRING_DECODES]}]. init_per_group(Name, Config) -> - [R,D,A,C] = ?util:name(Name), - G = #group{client_encoding = R, - client_dict0 = dict0(D), - server_encoding = A, - server_container = C}, - [{group, G} | Config]. + case ?util:name(Name) of + [R,D,A,C,SD,CD] -> + G = #group{client_service = [$C|?util:unique_string()], + client_encoding = R, + client_dict0 = dict0(D), + client_strings = CD, + server_service = [$S|?util:unique_string()], + server_encoding = A, + server_container = C, + server_strings = SD}, + [{group, G} | Config]; + _ -> + Config + end. end_per_group(_, _) -> ok. @@ -319,18 +351,26 @@ tc() -> start(_Config) -> ok = diameter:start(). -start_services(_Config) -> - ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)), - ok = diameter:start_service(?CLIENT, [{sequence, ?CLIENT_MASK} - | ?SERVICE(?CLIENT)]). +start_services(Config) -> + #group{client_service = CN, + client_strings = CD, + server_service = SN, + server_strings = SD} + = group(Config), + ok = diameter:start_service(SN, ?SERVICE(SN, SD)), + ok = diameter:start_service(CN, [{sequence, ?CLIENT_MASK} + | ?SERVICE(CN, CD)]). add_transports(Config) -> - LRef = ?util:listen(?SERVER, + #group{client_service = CN, + server_service = SN} + = group(Config), + LRef = ?util:listen(SN, tcp, [{capabilities_cb, fun capx/2}, {spawn_opt, [{min_heap_size, 8096}]}, {applications, apps(rfc3588)}]), - Cs = [?util:connect(?CLIENT, + Cs = [?util:connect(CN, tcp, LRef, [{id, Id}, @@ -354,12 +394,18 @@ outstanding(_Config) -> is_atom(element(1,T))]. remove_transports(Config) -> + #group{client_service = CN, + server_service = SN} + = group(Config), [LRef | Cs] = ?util:read_priv(Config, "transport"), - [?util:disconnect(?CLIENT, C, ?SERVER, LRef) || C <- Cs]. + [?util:disconnect(CN, C, SN, LRef) || C <- Cs]. -stop_services(_Config) -> - ok = diameter:stop_service(?CLIENT), - ok = diameter:stop_service(?SERVER). +stop_services(Config) -> + #group{client_service = CN, + server_service = SN} + = group(Config), + ok = diameter:stop_service(CN), + ok = diameter:stop_service(SN). %% Ensure even transports have been removed from request table. empty(_Config) -> @@ -439,8 +485,9 @@ send_arbitrary(Config) -> ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), {'AVP', [#diameter_avp{name = 'Product-Name', - value = "XXX"}]} - = lists:last(Avps). + value = V}]} + = lists:last(Avps), + "XXX" = string(V, Config). %% Send an unknown AVP (to some client) and check that it comes back. send_unknown(Config) -> @@ -594,9 +641,11 @@ send_nopeer(Config) -> {error, no_connection} = call(Config, Req, [{extra, [?EXTRA]}]). %% Send something on an unconfigured application. -send_noapp(_Config) -> +send_noapp(Config) -> + #group{client_service = CN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}], - {error, no_connection} = diameter:call(?CLIENT, unknown_alias, Req). + {error, no_connection} = diameter:call(CN, unknown_alias, Req). %% Send something that's discarded by prepare_request. send_discard(Config) -> @@ -608,8 +657,10 @@ send_any_1(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], {error, no_connection} = call(Config, Req, [{filter, {any, []}}]). send_any_2(Config) -> + #group{server_service = SN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}, - {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], + {'Destination-Host', [?HOST(SN, "unknown.org")]}], ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req, [{filter, {any, [host, realm]}}]). @@ -621,8 +672,10 @@ send_all_1(Config) -> = call(Config, Req, [{filter, {all, [{host, any}, {realm, Realm}]}}]). send_all_2(Config) -> + #group{server_service = SN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}, - {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], + {'Destination-Host', [?HOST(SN, "unknown.org")]}], {error, no_connection} = call(Config, Req, [{filter, {all, [host, realm]}}]). @@ -655,8 +708,10 @@ send_encode_error(Config) -> %% Send with filtering and expect success. send_destination_1(Config) -> + #group{server_service = SN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}, - {'Destination-Host', [?HOST(?SERVER, ?REALM)]}], + {'Destination-Host', [?HOST(SN, ?REALM)]}], ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [host, realm]}}]). send_destination_2(Config) -> @@ -672,8 +727,10 @@ send_destination_3(Config) -> {error, no_connection} = call(Config, Req, [{filter, {all, [host, realm]}}]). send_destination_4(Config) -> + #group{server_service = SN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}, - {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], + {'Destination-Host', [?HOST(SN, "unknown.org")]}], {error, no_connection} = call(Config, Req, [{filter, {all, [host, realm]}}]). @@ -685,8 +742,10 @@ send_destination_5(Config) -> ?answer_message(?REALM_NOT_SERVED) = call(Config, Req). send_destination_6(Config) -> + #group{server_service = SN} + = group(Config), Req = ['STR', {'Termination-Cause', ?LOGOUT}, - {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], + {'Destination-Host', [?HOST(SN, "unknown.org")]}], ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req). @@ -748,16 +807,31 @@ send_anything(Config) -> %% =========================================================================== +group(Config) -> + #group{} = proplists:get_value(group, Config). + +string(V, Config) -> + #group{client_strings = B} = group(Config), + decode(V,B). + +decode(S, true) + when is_list(S) -> + S; +decode(B, false) + when is_binary(B) -> + binary_to_list(B). + call(Config, Req) -> call(Config, Req, []). call(Config, Req, Opts) -> Name = proplists:get_value(testcase, Config), - #group{client_encoding = ReqEncoding, + #group{client_service = CN, + client_encoding = ReqEncoding, client_dict0 = Dict0} = Group - = proplists:get_value(group, Config), - diameter:call(?CLIENT, + = group(Config), + diameter:call(CN, dict(Req, Dict0), msg(Req, ReqEncoding, Dict0), [{extra, [{Name, Group}, diameter_lib:now()]} | Opts]). @@ -844,35 +918,38 @@ peer_down(_SvcName, _Peer, State) -> %% pick_peer/6-7 -pick_peer(Peers, _, ?CLIENT, _State, {Name, Group}, _) +pick_peer(Peers, _, [$C|_], _State, {Name, Group}, _) when Name /= send_detach -> find(Group, Peers). -pick_peer(_Peers, _, ?CLIENT, _State, {send_nopeer, _}, _, ?EXTRA) -> +pick_peer(_Peers, _, [$C|_], _State, {send_nopeer, _}, _, ?EXTRA) -> false; -pick_peer(Peers, _, ?CLIENT, _State, {send_detach, Group}, _, {_,_}) -> +pick_peer(Peers, _, [$C|_], _State, {send_detach, Group}, _, {_,_}) -> find(Group, Peers). -find(#group{server_encoding = A, server_container = C}, Peers) -> +find(#group{client_service = CN, + server_encoding = A, + server_container = C}, + Peers) -> Id = {A,C}, - [P] = [P || P <- Peers, id(Id, P)], + [P] = [P || P <- Peers, id(Id, P, CN)], {ok, P}. -id(Id, {Pid, _Caps}) -> +id(Id, {Pid, _Caps}, SvcName) -> [{ref, _}, {type, _}, {options, Opts} | _] - = diameter:service_info(?CLIENT, Pid), + = diameter:service_info(SvcName, Pid), lists:member({id, Id}, Opts). %% prepare_request/5-6 -prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, {send_discard, _}, _) -> +prepare_request(_Pkt, [$C|_], {_Ref, _Caps}, {send_discard, _}, _) -> {discard, unprepared}; -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {Name, Group}, _) -> +prepare_request(Pkt, [$C|_], {_Ref, Caps}, {Name, Group}, _) -> {send, prepare(Pkt, Caps, Name, Group)}. -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {send_detach, Group}, _, _) -> +prepare_request(Pkt, [$C|_], {_Ref, Caps}, {send_detach, Group}, _, _) -> {eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}. log(#diameter_packet{bin = Bin} = P, T) @@ -1043,10 +1120,10 @@ prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) -> %% handle_answer/6-7 -handle_answer(Pkt, Req, ?CLIENT, Peer, {Name, Group}, _) -> +handle_answer(Pkt, Req, [$C|_], Peer, {Name, Group}, _) -> answer(Pkt, Req, Peer, Name, Group). -handle_answer(Pkt, Req, ?CLIENT, Peer, {send_detach = Name, Group}, _, X) -> +handle_answer(Pkt, Req, [$C|_], Peer, {send_detach = Name, Group}, _, X) -> {Pid, Ref} = X, Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}. @@ -1075,13 +1152,13 @@ app(Req, _, Dict0) -> %% handle_error/6 -handle_error(timeout = Reason, _Req, ?CLIENT, _Peer, _, Time) -> +handle_error(timeout = Reason, _Req, [$C|_], _Peer, _, Time) -> Now = diameter_lib:now(), {Reason, {diameter_lib:timestamp(Time), diameter_lib:timestamp(Now), diameter_lib:micro_diff(Now, Time)}}; -handle_error(Reason, _Req, ?CLIENT, _Peer, _, _Time) -> +handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) -> {error, Reason}. %% handle_request/3 @@ -1089,7 +1166,7 @@ handle_error(Reason, _Req, ?CLIENT, _Peer, _, _Time) -> %% Note that diameter will set Result-Code and Failed-AVPs if %% #diameter_packet.errors is non-null. -handle_request(#diameter_packet{header = H, msg = M}, ?SERVER, {_Ref, Caps}) -> +handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) -> #diameter_header{end_to_end_id = EI, hop_by_hop_id = HI} = H, -- cgit v1.2.3 From 23cd65b80b395ad83e69618989ccc0bef13edd0a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 20 Mar 2015 22:22:41 +0100 Subject: Set {string_decode, false} in examples So as to do what's now recommended in diameter(1), in the grandparent commit. --- lib/diameter/examples/code/client.erl | 1 + lib/diameter/examples/code/relay.erl | 1 + lib/diameter/examples/code/server.erl | 1 + 3 files changed, 3 insertions(+) (limited to 'lib/diameter') diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl index be5b4cbba5..9b0972c8d1 100644 --- a/lib/diameter/examples/code/client.erl +++ b/lib/diameter/examples/code/client.erl @@ -68,6 +68,7 @@ {'Vendor-Id', 0}, {'Product-Name', "Client"}, {'Auth-Application-Id', [0]}, + {string_decode, false}, {application, [{alias, common}, {dictionary, diameter_gen_base_rfc6733}, {module, client_cb}]}]). diff --git a/lib/diameter/examples/code/relay.erl b/lib/diameter/examples/code/relay.erl index 0aa3cd06d3..e50d1a33f0 100644 --- a/lib/diameter/examples/code/relay.erl +++ b/lib/diameter/examples/code/relay.erl @@ -49,6 +49,7 @@ {'Vendor-Id', 193}, {'Product-Name', "RelayAgent"}, {'Auth-Application-Id', [16#FFFFFFFF]}, + {string_decode, false}, {application, [{alias, relay}, {dictionary, diameter_relay}, {module, relay_cb}]}]). diff --git a/lib/diameter/examples/code/server.erl b/lib/diameter/examples/code/server.erl index 8c91e68895..0de94018f9 100644 --- a/lib/diameter/examples/code/server.erl +++ b/lib/diameter/examples/code/server.erl @@ -53,6 +53,7 @@ {'Vendor-Id', 193}, {'Product-Name', "Server"}, {'Auth-Application-Id', [0]}, + {string_decode, false}, {application, [{alias, common}, {dictionary, diameter_gen_base_rfc6733}, {module, server_cb}]}]). -- cgit v1.2.3 From d6de8b13a37bc454f6d5a425cdaff6a114b4168c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 21 Mar 2015 18:44:29 +0100 Subject: Set {restrict_connections, false} in example server Since there's no reason to reject a client that wants to establish multiple connections, given that diameter can handle it. --- lib/diameter/examples/code/server.erl | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/diameter') diff --git a/lib/diameter/examples/code/server.erl b/lib/diameter/examples/code/server.erl index 0de94018f9..b292c7afdb 100644 --- a/lib/diameter/examples/code/server.erl +++ b/lib/diameter/examples/code/server.erl @@ -53,6 +53,7 @@ {'Vendor-Id', 193}, {'Product-Name', "Server"}, {'Auth-Application-Id', [0]}, + {restrict_connections, false}, {string_decode, false}, {application, [{alias, common}, {dictionary, diameter_gen_base_rfc6733}, -- cgit v1.2.3 From 949cec3fdcf94310aa567921ac2ea37569beb970 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 21 Mar 2015 19:10:19 +0100 Subject: Let examples override default service options To make them a bit more flexible. Can now do things like this: server:start([{'Product-Name', "Bob"}]), server:listen({tcp, [{capx_timeout, 2000}]}) Beware that the latter is completely different from this: server:listen(tcp, [{capx_timeout, 2000}]) --- lib/diameter/examples/code/client.erl | 15 ++++++++++++++- lib/diameter/examples/code/relay.erl | 11 ++++++++++- lib/diameter/examples/code/server.erl | 15 ++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl index 9b0972c8d1..844c9cdbdd 100644 --- a/lib/diameter/examples/code/client.erl +++ b/lib/diameter/examples/code/client.erl @@ -41,6 +41,7 @@ -include_lib("diameter/include/diameter_gen_base_rfc6733.hrl"). -export([start/1, %% start a service + start/2, %% connect/2, %% add a connecting transport call/1, %% send using the record encoding cast/1, %% send using the list encoding and detached @@ -77,11 +78,23 @@ start(Name) when is_atom(Name) -> - node:start(Name, ?SERVICE(Name)). + start(Name, []); + +start(Opts) + when is_list(Opts) -> + start(?DEF_SVC_NAME, Opts). + +%% start/0 start() -> start(?DEF_SVC_NAME). +%% start/2 + +start(Name, Opts) -> + node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name), + false == lists:keymember(K, 1, Opts)]). + %% connect/2 connect(Name, T) -> diff --git a/lib/diameter/examples/code/relay.erl b/lib/diameter/examples/code/relay.erl index e50d1a33f0..7bc46dc68d 100644 --- a/lib/diameter/examples/code/relay.erl +++ b/lib/diameter/examples/code/relay.erl @@ -32,6 +32,7 @@ -module(relay). -export([start/1, + start/2, listen/2, connect/2, stop/1]). @@ -58,11 +59,19 @@ start(Name) when is_atom(Name) -> - node:start(Name, ?SERVICE(Name)). + start(Name, []). + +%% start/1 start() -> start(?DEF_SVC_NAME). +%% start/2 + +start(Name, Opts) -> + node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name), + false == lists:keymember(K, 1, Opts)]). + %% listen/2 listen(Name, T) -> diff --git a/lib/diameter/examples/code/server.erl b/lib/diameter/examples/code/server.erl index b292c7afdb..f32cec594c 100644 --- a/lib/diameter/examples/code/server.erl +++ b/lib/diameter/examples/code/server.erl @@ -35,6 +35,7 @@ -module(server). -export([start/1, %% start a service + start/2, %% listen/2, %% add a listening transport stop/1]). %% stop a service @@ -63,11 +64,23 @@ start(Name) when is_atom(Name) -> - node:start(Name, ?SERVICE(Name)). + start(Name, []); + +start(Opts) + when is_list(Opts) -> + start(?DEF_SVC_NAME, Opts). + +%% start/0 start() -> start(?DEF_SVC_NAME). +%% start/2 + +start(Name, Opts) -> + node:start(Name, Opts ++ [T || {K,_} = T <- ?SERVICE(Name), + false == lists:keymember(K, 1, Opts)]). + %% listen/2 listen(Name, T) -> -- cgit v1.2.3