diff options
Diffstat (limited to 'lib/diameter')
-rw-r--r-- | lib/diameter/doc/src/diameter.xml | 12 | ||||
-rw-r--r-- | lib/diameter/doc/src/notes.xml | 250 | ||||
-rw-r--r-- | lib/diameter/examples/code/peer.erl | 2 | ||||
-rw-r--r-- | lib/diameter/include/diameter_gen.hrl | 88 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_codec.erl | 30 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_peer_fsm.erl | 13 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_service.erl | 130 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_traffic.erl | 118 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_watchdog.erl | 36 | ||||
-rw-r--r-- | lib/diameter/src/compiler/diameter_codegen.erl | 33 | ||||
-rw-r--r-- | lib/diameter/src/diameter.appup.src | 34 | ||||
-rw-r--r-- | lib/diameter/src/info/diameter_dbg.erl | 38 | ||||
-rw-r--r-- | lib/diameter/test/diameter_config_SUITE.erl | 2 | ||||
-rw-r--r-- | lib/diameter/test/diameter_event_SUITE.erl | 30 | ||||
-rw-r--r-- | lib/diameter/test/diameter_transport_SUITE.erl | 2 | ||||
-rw-r--r-- | lib/diameter/vsn.mk | 2 |
16 files changed, 662 insertions, 158 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index ab9ad25a3a..00b54ffbc4 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -500,6 +500,18 @@ Matches only those peers matched by each filter in the specified list.</p> <p> Matches only those peers matched by at least one filter in the specified list.</p> + +<p> +The resulting peer list will be in match order, peers matching the +first filter of the list sorting before those matched by the second, +and so on. +For example, the following filter causes peers matching both the host +and realm filters to be presented before those matching only the realm +filter.</p> + +<pre> +{any, [{all, [host, realm]}, realm]} +</pre> </item> </taglist> diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index 68e69dbfeb..e6ac332c10 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -42,6 +42,256 @@ first.</p> <!-- ===================================================================== --> +<section><title>diameter 1.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix remote diameter_request table leak.</p> + <p> + An outgoing request whose pick_peer callback selected a + transport on another node resulted in an orphaned table + entry on that node.</p> + <p> + Own Id: OTP-12196</p> + </item> + <item> + <p> + Fix handling of 3xxx Result-Code without E-bit.</p> + <p> + OTP-12233 broke the population of the errors field of the + diameter_packet record when an incoming request with an + E-bit/Result-Code mismatch was detected, causing a + 4-tuple to be inserted as Result-Code in a diameter_avp + record.</p> + <p> + Own Id: OTP-12233</p> + </item> + <item> + <p> + Fix ignored connect timer.</p> + <p> + There are two timers governing the establishment of peer + connections: connect_timer and watchdog_timer. The former + is the RFC 6733 Tc timer, and is used at initial + connection establishment. The latter is RFC 3539 TwInit, + and is used for connection reestablishment. A connecting + transport erroneously used watchdog_timer in both cases.</p> + <p> + Own Id: OTP-12281 Aux Id: seq12728 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Order candidate peers in pick_peer callbacks.</p> + <p> + The order of candidate peers presented to a + diameter_app(3) pick_peer callback has previously not + been documented, but there are use cases that are + simplified by an ordering. The order is now determined by + the filter.</p> + <p> + Own Id: OTP-12308</p> + </item> + </list> + </section> + +</section> + +<section><title>diameter 1.7.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Don't leave extra bit in decoded AVP data.</p> + <p> + An extra bit could be communicated in the data field of a + diameter_avp record in the case of length errors. Of no + consequence for code using the record encoding of + Diameter messages, but code examining diameter_avp + records would see this bit.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-12074</p> + </item> + <item> + <p> + Fix counting of outgoing requests and answers setting the + E-bit.</p> + <p> + OTP-11721 broke these counters for all outgoing requests + except DWR, and caused answers setting the E-bit to be + counted as unknown messages.</p> + <p> + Own Id: OTP-12080</p> + </item> + <item> + <p> + Fix Failed-AVP decode.</p> + <p> + The best-effort decode only worked for AVPs in the common + dictionary, not for those in the dictionary of the + application identified in the Diameter Header of the + answer message in question.</p> + <p> + Failed-AVP in an answer decoded with the RFC 3588 common + dictionary (diameter_gen_base_rfc3588) was regarded as an + error. The RFC 6733 dictionary was unaffected.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-12094</p> + </item> + </list> + </section> + +</section> + +<section><title>diameter 1.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Improve robustness.</p> + <p> + Counters returned by diameter:service_info/2 now only + count messages known to the dictionary in question, so + that an attacker cannot cause arbitrarily many counters + to be created.</p> + <p> + Messages to the Erlang log have been minimized, and those + related to traffic have been removed entirely since an + attacker could cause a node to be logged to death. + Consequently, the default answer_errors configuration has + been changed from report to discard. A service needs to + be restarted for the change in default to take effect.</p> + <p> + Own Id: OTP-11721</p> + </item> + <item> + <p> + Fix request table leak.</p> + <p> + Outgoing Diameter requests are stored in a table until an + answer is received or times out. Calling + diameter:stop_service/1 before this took place would + orphan the entries, resulting in a memory leak.</p> + <p> + Own Id: OTP-11893</p> + </item> + <item> + <p> + Fix broken SCTP transport.</p> + <p> + OTP-11593 caused the sending of answer messages over SCTP + to fail.</p> + <p> + Own Id: OTP-11901 Aux Id: OTP-11593 </p> + </item> + <item> + <p> + Fix watchdog process leak.</p> + <p> + A failed capabilities exchange on a listening transport + would orphan a process, causing a memory leak.</p> + <p> + Own Id: OTP-11934</p> + </item> + <item> + <p> + Fix incorrect handling of incoming DPR.</p> + <p> + In the case of a listening transport, a reconnection by a + peer following DPR could transition the watchdog state to + REOPEN instead of OKAY.</p> + <p> + Own Id: OTP-11938</p> + </item> + <item> + <p> + Fix handling of AVP length errors on unknown AVPs.</p> + <p> + An AVP (Header) length that pointed past the end of the + message was not flagged as a 5014 error in this case. + Moreover, encoding such an AVP in the Failed-AVP of an + answer message as a consequence of other errors (eg. + M-bit, resulting in 5001) failed if the AVP contained a + complete header.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-11946</p> + </item> + <item> + <p> + Fix broken check in dictionary compilation.</p> + <p> + That an AVP specified in the content of a @codecs or + @custom_types section was undefined went undetected, + causing compilation to fail when attempting to lookup the + AVP's type.</p> + <p> + Own Id: OTP-11958</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add result code counters for CEA, DWA, and DPA.</p> + <p> + In addition to the existing result code counters on other + answer messages.</p> + <p> + Own Id: OTP-11891</p> + </item> + <item> + <p> + Add best-effort decode of AVPs within Failed-AVP.</p> + <p> + OTP-11007 disabled the decode of AVPs in Failed-AVP since + errors could cause the decode of Failed-AVP itself to + fail. Component AVPs are now decoded if possible, + otherwise not. AVPs of type Grouped are decoded as much + as possible, as deeply as possible.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-11936 Aux Id: OTP-11007 </p> + </item> + <item> + <p> + Add counters for encode errors in outgoing Diameter + messages.</p> + <p> + In addition to the existing counters on decode errors. + The latter now count independently of result codes in + answer messages since decode errors do not preclude the + presence of a result code.</p> + <p> + Own Id: OTP-11937</p> + </item> + </list> + </section> + +</section> + <section><title>diameter 1.6</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/diameter/examples/code/peer.erl b/lib/diameter/examples/code/peer.erl index b4ee17e4b7..7519abfb2c 100644 --- a/lib/diameter/examples/code/peer.erl +++ b/lib/diameter/examples/code/peer.erl @@ -74,7 +74,7 @@ start(Name, Opts) | {error, term()}. connect(Name, T) -> - diameter:add_transport(Name, {connect, [{reconnect_timer, 5000} + diameter:add_transport(Name, {connect, [{connect_timer, 5000} | client(T)]}). %% listen/2 diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 7e91ce375f..bc25f7d472 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -311,19 +311,55 @@ d(Name, Avp, Acc) -> Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP %% decode is packed into 'AVP'. - try avp(decode, Data, AvpName) of + Mod = dict(Failed), %% Dictionary to decode in. + + try Mod:avp(decode, Data, AvpName) of V -> {Avps, T} = Acc, {H, A} = ungroup(V, Avp), {[H | Avps], pack_avp(Name, A, T)} catch error: Reason -> - d(undefined == Failed orelse is_failed(), Reason, Name, Avp, Acc) + d(undefined == Failed orelse is_failed(), + Reason, + Name, + trim(Avp), + Acc) after reset(?STRICT_KEY, Strict), reset(?FAILED_KEY, Failed) end. +%% trim/1 +%% +%% Remove any extra bit that was added in diameter_codec to induce a +%% 5014 error. + +trim(#diameter_avp{data = <<0:1, Bin/binary>>} = Avp) -> + Avp#diameter_avp{data = Bin}; + +trim(Avp) -> + Avp. + +%% dict/1 +%% +%% Retrieve the dictionary for the best-effort decode of Failed-AVP, +%% as put by diameter_codec:decode/2. See that function for the +%% explanation. + +dict(true) -> + case get({diameter_codec, dictionary}) of + undefined -> + ?MODULE; + Mod -> + Mod + end; + +dict(_) -> + ?MODULE. + +%% d/5 + %% Ignore a decode error within Failed-AVP ... d(true, _, Name, Avp, Acc) -> decode_AVP(Name, Avp, Acc); @@ -341,6 +377,8 @@ d(false, Reason, Name, Avp, {Avps, Acc}) -> {Rec, Failed} = Acc, {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}. +%% relax/2 + %% Set false in the process dictionary as soon as we see a Grouped AVP %% that doesn't set the M-bit, so that is_strict() can say whether or %% not to ignore the M-bit on an encapsulated AVP. @@ -357,22 +395,23 @@ relax(_, _) -> is_strict() -> false /= getr(?STRICT_KEY). +%% relax/1 +%% %% Set true in the process dictionary as soon as we see Failed-AVP. %% Matching on 'Failed-AVP' assumes that this is the RFC AVP. %% Strictly, this doesn't need to be the case. + relax('Failed-AVP') -> - case getr(?FAILED_KEY) of - undefined -> - putr(?FAILED_KEY, true); - true = Yes -> - Yes - end; + is_failed() orelse putr(?FAILED_KEY, true); + relax(_) -> is_failed(). is_failed() -> true == getr(?FAILED_KEY). +%% reset/2 + reset(Key, undefined) -> eraser(Key); reset(_, _) -> @@ -453,8 +492,8 @@ pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) -> {Rec, Failed} = Acc, {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]}; -pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> - case pack_arity(Name, M) of +pack_AVP(Name, #diameter_avp{is_mandatory = M, name = AvpName} = Avp, Acc) -> + case pack_arity(Name, AvpName, M) of 0 -> {Rec, Failed} = Acc, {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; @@ -462,10 +501,13 @@ pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> pack(Arity, 'AVP', Avp, Acc) end. -%% Give Failed-AVP special treatment since it'll contain any -%% unrecognized mandatory AVP's. -pack_arity(Name, M) -> - NF = Name /= 'Failed-AVP' andalso not is_failed(), +%% Give Failed-AVP special treatment since (1) it'll contain any +%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to +%% allow for Failed-AVP in an answer-message. + +pack_arity(Name, AvpName, M) -> + IsFailed = Name == 'Failed-AVP' orelse is_failed(), + %% Not testing just Name /= 'Failed-AVP' means we're changing the %% packing of AVPs nested within Failed-AVP, but the point of %% ignoring errors within Failed-AVP is to decode as much as @@ -473,12 +515,18 @@ pack_arity(Name, M) -> %% packed into a dedicated field defeats that point. Note that we %% can't just test not is_failed() since this will be 'true' when %% packing an unknown AVP directly within Failed-AVP. - case NF andalso M andalso is_strict() of - true -> - 0; - false -> - avp_arity(Name, 'AVP') - end. + + pack_arity(IsFailed + orelse {Name, AvpName} == {'answer-message', 'Failed-AVP'} + orelse not M + orelse not is_strict(), + Name). + +pack_arity(true, Name) -> + avp_arity(Name, 'AVP'); + +pack_arity(false, _) -> + 0. %% 3588: %% diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 06a4f5de64..a2b04bfd63 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -237,15 +237,35 @@ rec2msg(Mod, Rec) -> %% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors. --spec decode(module(), #diameter_packet{} | binary()) +-spec decode(module() | {module(), module()}, #diameter_packet{} | binary()) -> #diameter_packet{}. +%% An Answer setting the E-bit. The application dictionary is needed +%% for the best-effort decode of Failed-AVP, and the best way to make +%% this available to the AVP decode in diameter_gen.hrl, without +%% having to rewrite the entire codec generation, is to place it in +%% the process dictionary. It's the code in diameter_gen.hrl (that's +%% included by every generated codec module) that looks for the entry. +%% Not ideal, but it solves the problem relatively simply. +decode({Mod, Mod}, Pkt) -> + decode(Mod, Pkt); +decode({Mod, AppMod}, Pkt) -> + Key = {?MODULE, dictionary}, + put(Key, AppMod), + try + decode(Mod, Pkt) + after + erase(Key) + end; + +%% Or not: a request, or an answer not setting the E-bit. decode(Mod, Pkt) -> decode(Mod:id(), Mod, Pkt). -%% If we're a relay application then just extract the avp's without -%% any decoding of their data since we don't know the application in -%% question. +%% decode/3 + +%% Relay application: just extract the avp's without any decoding of +%% their data since we don't know the application in question. decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) -> case collect_avps(Pkt) of {E, As} -> @@ -274,6 +294,8 @@ decode(Id, Mod, Bin) when is_binary(Bin) -> decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}). +%% decode_avps/4 + decode_avps(MsgName, Mod, Pkt, {E, Avps}) -> ?LOG(invalid_avp_length, Pkt#diameter_packet.header), #diameter_packet{errors = Failed} diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 31e570ae20..ee6e7dd89e 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -225,8 +225,8 @@ start_transport(Addrs0, T) -> erlang:monitor(process, TPid), q_next(TPid, Addrs0, Tmo, Data), {TPid, Addrs}; - No -> - exit({shutdown, No}) + {error, No} -> + exit({shutdown, {no_connection, No}}) end. svc(#diameter_service{capabilities = LCaps0} = Svc, Addrs) -> @@ -368,11 +368,8 @@ transition({diameter, {TPid, connected}}, %% message. This may be followed by an incoming message which arrived %% before the transport was killed and this can't be distinguished %% from one from the transport that's been started to replace it. -transition({diameter, {_, connected}}, _) -> - {stop, connection_timeout}; -transition({diameter, {_, connected, _}}, _) -> - {stop, connection_timeout}; -transition({diameter, {_, connected, _, _}}, _) -> +transition({diameter, T}, _) + when tuple_size(T) < 5, connected == element(2,T) -> {stop, connection_timeout}; %% Connection has timed out: start an alternate. @@ -477,6 +474,7 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, hop_by_hop_id = Hid}} = Pkt = encode(CER, Dict), + incr(send, Pkt, Dict), send(TPid, Pkt), ?LOG(send, 'CER'), start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}). @@ -1100,6 +1098,7 @@ send_dpr(Reason, Opts, #state{transport = TPid, {'Origin-Realm', OR}, {'Disconnect-Cause', Cause}], Dict), + incr(send, Pkt, Dict), send(TPid, Pkt), dpa_timer(Tmo), ?LOG(send, 'DPR'), diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index b7cd311e02..76b05a2ad4 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -1460,42 +1460,52 @@ pick_peer(Local, peers(Alias, RH, Filter, Peers) -> case ?Dict:find(Alias, Peers) of {ok, L} -> - ps(L, RH, Filter, {[],[]}); + filter(L, RH, Filter); error -> [] end. -%% Place a peer whose Destination-Host/Realm matches those of the -%% request at the front of the result list. Could add some sort of -%% 'sort' option to allow more control. - -ps([], _, _, {Ys, Ns}) -> - lists:reverse(Ys, Ns); -ps([{_TPid, #diameter_caps{} = Caps} = TC | Rest], RH, Filter, Acc) -> - ps(Rest, RH, Filter, pacc(caps_filter(Caps, RH, Filter), - caps_filter(Caps, RH, {all, [host, realm]}), - TC, - Acc)). - -pacc(true, true, Peer, {Ts, Fs}) -> - {[Peer|Ts], Fs}; -pacc(true, false, Peer, {Ts, Fs}) -> - {Ts, [Peer|Fs]}; -pacc(_, _, _, Acc) -> - Acc. +%% filter/3 +%% +%% Return peers in match order. -%% caps_filter/3 +filter(Peers, RH, Filter) -> + {Ts, _} = fltr(Peers, RH, Filter), + Ts. + +%% fltr/4 + +fltr(Peers, _, none) -> + {Peers, []}; -caps_filter(C, RH, {neg, F}) -> - not caps_filter(C, RH, F); +fltr(Peers, RH, {neg, F}) -> + {Ts, Fs} = fltr(Peers, RH, F), + {Fs, Ts}; -caps_filter(C, RH, {all, L}) +fltr(Peers, RH, {all, L}) when is_list(L) -> - lists:all(fun(F) -> caps_filter(C, RH, F) end, L); + lists:foldl(fun(F,A) -> fltr_all(F, A, RH) end, + {Peers, []}, + L); -caps_filter(C, RH, {any, L}) +fltr(Peers, RH, {any, L}) when is_list(L) -> - lists:any(fun(F) -> caps_filter(C, RH, F) end, L); + lists:foldl(fun(F,A) -> fltr_any(F, A, RH) end, + {[], Peers}, + L); + +fltr(Peers, RH, F) -> + lists:partition(fun({_,C}) -> caps_filter(C, RH, F) end, Peers). + +fltr_all(F, {Ts0, Fs0}, RH) -> + {Ts1, Fs1} = fltr(Ts0, RH, F), + {Ts1, Fs0 ++ Fs1}. + +fltr_any(F, {Ts0, Fs0}, RH) -> + {Ts1, Fs1} = fltr(Fs0, RH, F), + {Ts0 ++ Ts1, Fs1}. + +%% caps_filter/3 caps_filter(#diameter_caps{origin_host = {_,OH}}, [_,DH], host) -> eq(undefined, DH, OH); @@ -1508,9 +1518,6 @@ caps_filter(C, _, Filter) -> %% caps_filter/2 -caps_filter(_, none) -> - true; - caps_filter(#diameter_caps{origin_host = {_,OH}}, {host, H}) -> eq(any, H, OH); @@ -1573,7 +1580,8 @@ transports(#state{watchdogT = WatchdogT}) -> -define(OTHER_INFO, [connections, name, peers, - statistics]). + statistics, + info]). service_info(Item, S) when is_atom(Item) -> @@ -1663,6 +1671,7 @@ complete_info(Item, #state{service = Svc} = S) -> keys -> ?ALL_INFO ++ ?CAP_INFO ++ ?OTHER_INFO; all -> service_info(?ALL_INFO, S); statistics -> info_stats(S); + info -> info_info(S); connections -> info_connections(S); peers -> info_peers(S) end. @@ -1745,12 +1754,11 @@ peer_acc(PeerT, Acc, #watchdog{pid = Pid, state = WS, started = At, peer = TPid}) -> - dict:append(Ref, - [{type, Type}, - {options, Opts}, - {watchdog, {Pid, At, WS}} - | info_peer(PeerT, TPid, WS)], - Acc). + Info = [{type, Type}, + {options, Opts}, + {watchdog, {Pid, At, WS}} + | info_peer(PeerT, TPid, WS)], + dict:append(Ref, Info ++ [{info, info_process_info(Info)}], Acc). info_peer(PeerT, TPid, WS) when is_pid(TPid), WS /= ?WD_DOWN -> @@ -1762,6 +1770,49 @@ info_peer(PeerT, TPid, WS) info_peer(_, _, _) -> []. +info_process_info(Info) -> + lists:flatmap(fun ipi/1, Info). + +ipi({watchdog, {Pid, _, _}}) -> + info_pid(Pid); + +ipi({peer, {Pid, _}}) -> + info_pid(Pid); + +ipi({port, [{owner, Pid} | _]}) -> + info_pid(Pid); + +ipi(_) -> + []. + +info_pid(Pid) -> + case process_info(Pid, [message_queue_len, memory, binary]) of + undefined -> + []; + L -> + [{Pid, lists:map(fun({K,V}) -> {K, map_info(K,V)} end, L)}] + end. + +%% The binary list consists of 3-tuples {Ptr, Size, Count}, where Ptr +%% is a C pointer value, Size is the size of a referenced binary in +%% bytes, and Count is a global reference count. The same Ptr can +%% occur multiple times, once for each reference on the process heap. +%% In this case, the corresponding tuples will have Size in common but +%% Count may differ just because no global lock is taken when the +%% value is retrieved. +%% +%% The list can be quite large, and we aren't often interested in the +%% pointers or counts, so whittle this down to the number of binaries +%% referenced and their total byte count. +map_info(binary, L) -> + SzD = lists:foldl(fun({P,S,_}, D) -> dict:store(P,S,D) end, + dict:new(), + L), + {dict:size(SzD), dict:fold(fun(_,S,N) -> S + N end, 0, SzD)}; + +map_info(_, T) -> + T. + %% The point of extracting the config here is so that 'transport' info %% has one entry for each transport ref, the peer table only %% containing entries that have a living watchdog. @@ -1819,6 +1870,13 @@ mk_app(#diameter_app{} = A) -> info_pending(#state{} = S) -> diameter_traffic:pending(transports(S)). +%% info_info/1 +%% +%% Extract process_info from connections info. + +info_info(S) -> + [I || L <- conn_list(S), {info, I} <- L]. + %% info_connections/1 %% %% One entry per transport connection. Statistics for each entry are diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 5fac61f416..3b62afca47 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -129,6 +129,11 @@ incr(Dir, #diameter_header{} = H, TPid, Dict) -> %% incr_error/4 %% --------------------------------------------------------------------------- +%% Identify messages using the application dictionary, not the encode +%% dictionary, which may differ in the case of answer-message. +incr_error(Dir, T, Pid, {_Dict, AppDict}) -> + incr_error(Dir, T, Pid, AppDict); + %% Decoded message without errors. incr_error(recv, #diameter_packet{errors = []}, _, _) -> ok; @@ -169,7 +174,7 @@ incr_error(Dir, Id, TPid) -> incr_rc(Dir, Pkt, TPid, Dict0) -> try - incr_rc(Dir, Pkt, Dict0, TPid, Dict0) + incr_result(Dir, Pkt, TPid, {Dict0, Dict0, Dict0}) catch exit: {E,_} when E == no_result_code; E == invalid_error_bit -> @@ -471,7 +476,7 @@ send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application #diameter_packet{errors = [RC|_]} = Pkt, send_A(answer_message(RC, Caps, Dict0, Pkt), TPid, - Dict0, + {Dict0, Dict0}, Pkt, [], []); @@ -479,7 +484,7 @@ send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) -> send_A(answer(T, Caps, Pkt, App, Dict0, RecvData), TPid, - Dict0, + {App#diameter_app.dictionary, Dict0}, Pkt, EvalPktFs, EvalFs); @@ -489,8 +494,8 @@ send_A(_, _, _, _) -> %% send_A/6 -send_A(T, TPid, Dict0, ReqPkt, EvalPktFs, EvalFs) -> - reply(T, TPid, Dict0, EvalPktFs, ReqPkt), +send_A(T, TPid, DictT, ReqPkt, EvalPktFs, EvalFs) -> + reply(T, TPid, DictT, EvalPktFs, ReqPkt), lists:foreach(fun diameter_lib:eval/1, EvalFs). %% answer/6 @@ -648,32 +653,32 @@ is_loop(Code, Vid, OH, Dict0, Avps) -> %% reply/5 %% Local answer ... -reply({Dict, Ans}, TPid, Dict0, Fs, ReqPkt) -> - reply(Ans, Dict, TPid, Dict0, Fs, ReqPkt); +reply({Dict, Ans}, TPid, {AppDict, Dict0}, Fs, ReqPkt) -> + local(Ans, TPid, {Dict, AppDict, Dict0}, Fs, ReqPkt); %% ... or relayed. reply(#diameter_packet{} = Pkt, TPid, _Dict0, Fs, _ReqPkt) -> eval_packet(Pkt, Fs), send(TPid, Pkt). -%% reply/6 +%% local/5 %% %% Send a locally originating reply. %% Skip the setting of Result-Code and Failed-AVP's below. This is %% undocumented and shouldn't be relied on. -reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt) +local([Msg], TPid, DictT, Fs, ReqPkt) when is_list(Msg); is_tuple(Msg) -> - reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt#diameter_packet{errors = []}); + local(Msg, TPid, DictT, Fs, ReqPkt#diameter_packet{errors = []}); -reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) -> - Pkt = encode(Dict, +local(Msg, TPid, {Dict, AppDict, Dict0} = DictT, Fs, ReqPkt) -> + Pkt = encode({Dict, AppDict}, TPid, reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0), Fs), - incr(send, Pkt, TPid, Dict), - incr_rc(send, Pkt, Dict, TPid, Dict0), %% count outgoing + incr(send, Pkt, TPid, AppDict), + incr_result(send, Pkt, TPid, DictT), %% count outgoing send(TPid, Pkt). %% reset/3 @@ -1038,29 +1043,29 @@ find(Pred, [H|T]) -> %% code, the missing vendor id, and a zero filled payload of the minimum %% required length for the omitted AVP will be added. -%% incr_rc/5 +%% incr_result/5 %% %% Increment a stats counter for result codes in incoming and outgoing %% answers. %% Outgoing message as binary: don't count. (Sending binaries is only %% partially supported.) -incr_rc(_, #diameter_packet{msg = undefined = No}, _, _, _) -> +incr_result(_, #diameter_packet{msg = undefined = No}, _, _) -> No; %% Incoming or outgoing. Outgoing with encode errors never gets here %% since encode fails. -incr_rc(Dir, Pkt, Dict, TPid, Dict0) -> +incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) -> #diameter_packet{header = #diameter_header{is_error = E} = Hdr, msg = Msg, errors = Es} = Pkt, - Id = msg_id(Hdr, Dict), + Id = msg_id(Hdr, AppDict), %% Count incoming decode errors. - recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, Dict), + recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict), %% Exit on a missing result code. T = rc_counter(Dict, Msg), @@ -1074,12 +1079,27 @@ incr_rc(Dir, Pkt, Dict, TPid, Dict0) -> incr(TPid, {Id, Dir, Ctr}), Ctr. -%% Only count on known keeps so as not to be vulnerable to attack: -%% there are 2^32 (application ids) * 2^24 (command codes) * 2 (R-bits) -%% = 2^57 Ids for an attacker to choose from. +%% msg_id/2 + +msg_id(#diameter_packet{header = H}, Dict) -> + msg_id(H, Dict); + +%% Only count on known keys so as not to be vulnerable to attack: +%% there are 2^32 (application ids) * 2^24 (command codes) = 2^56 +%% pairs for an attacker to choose from. msg_id(Hdr, Dict) -> {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr), - choose('' == Dict:msg_name(Code, 0 == R), unknown, Id). + case Dict:msg_name(Code, 0 == R) of + '' -> + unknown(Dict:id(), R); + _ -> + Id + end. + +unknown(?APP_ID_RELAY, R) -> + {relay, R}; +unknown(_, _) -> + unknown. %% No E-bit: can't be 3xxx. is_result(RC, false, _Dict0) -> @@ -1396,6 +1416,7 @@ send_R(Pkt0, packet = Pkt0}, try + incr(send, Pkt, TPid, Dict), TRef = send_request(TPid, Pkt, Req, SvcName, Timeout), Pid ! Ref, %% tell caller a send has been attempted handle_answer(SvcName, @@ -1431,14 +1452,14 @@ handle_answer(SvcName, App, {error, Req, Reason}) -> handle_error(App, Req, Reason, SvcName); handle_answer(SvcName, - #diameter_app{dictionary = Dict, + #diameter_app{dictionary = AppDict, id = Id} = App, {answer, Req, Dict0, Pkt}) -> - Mod = dict(Dict, Dict0, Pkt), - handle_A(errors(Id, diameter_codec:decode(Mod, Pkt)), + Dict = dict(AppDict, Dict0, Pkt), + handle_A(errors(Id, diameter_codec:decode({Dict, AppDict}, Pkt)), SvcName, - Mod, + Dict, Dict0, App, Req). @@ -1448,10 +1469,12 @@ handle_answer(SvcName, %% want to examine the answer? handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> - incr(recv, Pkt, TPid, Dict), + AppDict = App#diameter_app.dictionary, + + incr(recv, Pkt, TPid, AppDict), try - incr_rc(recv, Pkt, Dict, TPid, Dict0) %% count incoming + incr_result(recv, Pkt, TPid, {Dict, AppDict, Dict0}) %% count incoming of _ -> answer(Pkt, SvcName, App, Req) catch @@ -1461,13 +1484,15 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> %% a missing AVP. If both are optional in the dictionary %% then this isn't a decode error: just continue on. answer(Pkt, SvcName, App, Req); - exit: {invalid_error_bit, RC} -> + exit: {invalid_error_bit, {_, _, _, RC}} -> #diameter_packet{errors = Es} = Pkt, E = {5004, #diameter_avp{name = 'Result-Code', value = RC}}, answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req) end. +%% answer/4 + answer(Pkt, SvcName, #diameter_app{module = ModX, @@ -1568,13 +1593,18 @@ encode(Dict, TPid, Pkt, Fs) -> %% an encoded binary. This isn't the usual case and doesn't properly %% support retransmission but is useful for test. +encode(Dict, TPid, Pkt) + when is_atom(Dict) -> + encode({Dict, Dict}, TPid, Pkt); + %% A message to be encoded. -encode(Dict, TPid, #diameter_packet{bin = undefined} = Pkt) -> +encode(DictT, TPid, #diameter_packet{bin = undefined} = Pkt) -> + {Dict, AppDict} = DictT, try diameter_codec:encode(Dict, Pkt) catch exit: {diameter_codec, encode, T} = Reason -> - incr_error(send, T, TPid, Dict), + incr_error(send, T, TPid, AppDict), exit(Reason) end; @@ -1602,12 +1632,23 @@ send_request(TPid, #diameter_packet{} = Pkt, Req, SvcName, Timeout) -> %% send/1 -send({TPid, Pkt, #request{handler = Pid} = Req, SvcName, Timeout, TRef}) -> - Ref = send_request(TPid, - Pkt, - Req#request{handler = self()}, - SvcName, - Timeout), +send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) -> + Seqs = diameter_codec:sequence_numbers(Pkt), + Req = Req0#request{handler = self()}, + Ref = send_request(TPid, Pkt, Req, SvcName, Timeout), + + try + recv(TPid, Pid, TRef, Ref) + after + %% Remove only the entry for this specific send since a resend + %% from the originating node can pick another transport on + %% this one. + ets:delete_object(?REQUEST_TABLE, {Seqs, Req, Ref}) + end. + +%% recv/4 + +recv(TPid, Pid, TRef, Ref) -> receive {answer, _, _, _, _} = A -> Pid ! A; @@ -1683,6 +1724,7 @@ resend_request(Pkt0, caps = Caps}, ?LOG(retransmission, Pkt#diameter_packet.header), + incr(TPid, {msg_id(Pkt, Dict), send, retransmission}), TRef = send_request(TPid, Pkt, Req, SvcName, Tmo), {TRef, Req}. diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index eff5096745..b7f2d24941 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -255,11 +255,15 @@ close({'DOWN', _, process, TPid, {shutdown, Reason}}, close(_, _) -> ok. -event(_, #watchdog{status = T}, #watchdog{status = T}) -> - ok; - -event(_, #watchdog{transport = undefined}, #watchdog{transport = undefined}) -> +event(_, + #watchdog{status = From, transport = F}, + #watchdog{status = To, transport = T}) + when F == undefined, T == undefined; %% transport not started + From == initial, To == down; %% never really left INITIAL + From == To -> %% no state transition ok; +%% Note that there is no INITIAL -> DOWN transition in RFC 3539: ours +%% is just a consequence of stop. event(Msg, #watchdog{status = From, transport = F, parent = Pid}, @@ -411,7 +415,7 @@ transition({'DOWN', _, process, TPid, _Reason}, stop; %% ... or not. -transition({'DOWN', _, process, TPid, _Reason}, +transition({'DOWN', _, process, TPid, _Reason} = D, #watchdog{transport = TPid, status = T, restrict = {_,R}} @@ -422,20 +426,14 @@ transition({'DOWN', _, process, TPid, _Reason}, %% Close an accepting watchdog immediately if there's no %% restriction on the number of connections to the same peer: the - %% state machine never enters state REOPEN in this case. The - %% 'close' message (instead of stop) is so as not to bypass the - %% sending of messages to the service process in handle_info/2. - - if T /= initial, M == accept, not R -> - send(self(), close), - S#watchdog{status = down}; - T /= initial -> - set_watchdog(S#watchdog{status = down}); - M == connect -> - set_watchdog(S); - M == accept -> - send(self(), close), - S + %% state machine never enters state REOPEN in this case. + + if T == initial; + M == accept, not R -> + close(D, S0), + stop; + true -> + set_watchdog(S#watchdog{status = down}) end; %% Incoming message. diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 5a068c1a25..d91a776321 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -132,7 +132,7 @@ gen(parse, ParseD, _Mod) -> [?VERSION | ParseD]; gen(forms, ParseD, Mod) -> - pp(erl_forms(Mod, ParseD)); + preprocess(Mod, erl_forms(Mod, ParseD)); gen(hrl, ParseD, Mod) -> gen_hrl(Mod, ParseD); @@ -838,19 +838,19 @@ rec_name(Name, Prefix) -> Prefix ++ Name. %% =========================================================================== -%% pp/1 +%% preprocess/2 %% %% Preprocess forms as generated by 'forms' option. In particular, %% replace the include_lib attributes in generated forms by the %% corresponding forms, extracting the latter from an existing %% dictionary (diameter_gen_relay). The resulting forms can be %% compiled to beam using compile:forms/2 (which does no preprocessing -%% or it's own; DiY currently appears to be the only way to preprocess +%% of it's own; DiY currently appears to be the only way to preprocess %% a forms list). -pp(Forms) -> +preprocess(Mod, Forms) -> {_, Beam, _} = code:get_object_code(diameter_gen_relay), - pp(Forms, abstract_code(Beam)). + pp(Forms, remod(Mod, abstract_code(Beam))). pp(Forms, {ok, Code}) -> Files = files(Code, []), @@ -859,6 +859,25 @@ pp(Forms, {ok, Code}) -> pp(Forms, {error, Reason}) -> erlang:error({forms, Reason, Forms}). +%% Replace literal diameter_gen_relay atoms in the extracted forms. +%% ?MODULE for example. + +remod(Mod, L) + when is_list(L) -> + [remod(Mod, T) || T <- L]; + +remod(Mod, {atom, _, diameter_gen_relay} = T) -> + setelement(3, T, Mod); + +remod(Mod, T) + when is_tuple(T) -> + list_to_tuple(remod(Mod, tuple_to_list(T))); + +remod(_, T) -> + T. + +%% Replace include_lib by the corresponding forms. + include({attribute, _, include_lib, Path}, Files) -> Inc = filename:basename(Path), [{Inc, Forms}] = [T || {F, _} = T <- Files, F == Inc], %% expect one @@ -867,6 +886,8 @@ include({attribute, _, include_lib, Path}, Files) -> include(T, _) -> [T]. +%% Extract abstract code. + abstract_code(Beam) -> case beam_lib:chunks(Beam, [abstract_code]) of {ok, {_Mod, [{abstract_code, {_Vsn, Code}}]}} -> @@ -877,6 +898,8 @@ abstract_code(Beam) -> {E, Reason} end. +%% Extract filename/forms pairs for included forms. + files([{attribute, _, file, {Path, _}} | T], Acc) -> {Body, Rest} = lists:splitwith(fun({attribute, _, file, _}) -> false; (_) -> true diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index b7b9662383..881d25b5fb 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -43,10 +43,23 @@ {load_module, diameter_gen_base_rfc6733}, {load_module, diameter_gen_acct_rfc6733}, {load_module, diameter_gen_base_rfc3588}, - {load_module, diameter_gen_accounting}, + {load_module, diameter_gen_base_accounting}, {load_module, diameter_gen_relay}, {load_module, diameter_codec}, - {load_module, diameter_sctp}]} + {load_module, diameter_sctp}]}, + {"1.7", [{load_module, diameter_service}, %% 17.1 + {load_module, diameter_codec}, + {load_module, diameter_gen_base_rfc6733}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_relay}, + {load_module, diameter_traffic}, + {load_module, diameter_peer_fsm}]}, + {"1.7.1", [{load_module, diameter_traffic}, %% 17.3 + {load_module, diameter_watchdog}, + {load_module, diameter_peer_fsm}, + {load_module, diameter_service}]} ], [ {"0.9", [{restart_application, diameter}]}, @@ -67,7 +80,7 @@ {"1.6", [{load_module, diameter_sctp}, {load_module, diameter_codec}, {load_module, diameter_gen_relay}, - {load_module, diameter_gen_accounting}, + {load_module, diameter_gen_base_accounting}, {load_module, diameter_gen_base_rfc3588}, {load_module, diameter_gen_acct_rfc6733}, {load_module, diameter_gen_base_rfc6733}, @@ -75,6 +88,19 @@ {load_module, diameter_peer_fsm}, {load_module, diameter_watchdog}, {load_module, diameter_traffic}, - {load_module, diameter_lib}]} + {load_module, diameter_lib}]}, + {"1.7", [{load_module, diameter_peer_fsm}, + {load_module, diameter_traffic}, + {load_module, diameter_gen_relay}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, + {load_module, diameter_codec}, + {load_module, diameter_service}]}, + {"1.7.1", [{load_module, diameter_service}, + {load_module, diameter_peer_fsm}, + {load_module, diameter_watchdog}, + {load_module, diameter_traffic}]} ] }. diff --git a/lib/diameter/src/info/diameter_dbg.erl b/lib/diameter/src/info/diameter_dbg.erl index b5b3983afa..b536e5e80b 100644 --- a/lib/diameter/src/info/diameter_dbg.erl +++ b/lib/diameter/src/info/diameter_dbg.erl @@ -32,7 +32,8 @@ compiled/0, procs/0, latest/0, - nl/0]). + nl/0, + sizes/0]). -export([diameter_config/0, diameter_peer/0, @@ -69,7 +70,16 @@ -define(VALUES(Rec), tl(tuple_to_list(Rec))). %% ---------------------------------------------------------- -%% # table(TableName) +%% # sizes/0 +%% +%% Return sizes of named tables. +%% ---------------------------------------------------------- + +sizes() -> + [{T, ets:info(T, size)} || T <- ?LOCAL, T /= diameter_peer]. + +%% ---------------------------------------------------------- +%% # table/1 %% %% Pretty-print a diameter table. Returns the number of records %% printed, or undefined. @@ -97,7 +107,7 @@ split([F|Fs], [V|Vs]) -> {F, Fs, V, Vs}. %% ---------------------------------------------------------- -%% # TableName() +%% # TableName/0 %% ---------------------------------------------------------- -define(TABLE(Name), Name() -> table(Name)). @@ -111,7 +121,7 @@ split([F|Fs], [V|Vs]) -> ?TABLE(diameter_stats). %% ---------------------------------------------------------- -%% # tables() +%% # tables/0 %% %% Pretty-print diameter tables from all nodes. Returns the number of %% records printed. @@ -127,7 +137,7 @@ split(_, Fs, Vs) -> split(Fs, Vs). %% ---------------------------------------------------------- -%% # modules() +%% # modules/0 %% ---------------------------------------------------------- modules() -> @@ -140,49 +150,49 @@ appdir() -> [_|_] = code:lib_dir(?APP, ebin). %% ---------------------------------------------------------- -%% # versions() +%% # versions/0 %% ---------------------------------------------------------- versions() -> ?I:versions(modules()). %% ---------------------------------------------------------- -%% # versions() +%% # version_info/0 %% ---------------------------------------------------------- version_info() -> ?I:version_info(modules()). %% ---------------------------------------------------------- -%% # compiled() +%% # compiled/0 %% ---------------------------------------------------------- compiled() -> ?I:compiled(modules()). %% ---------------------------------------------------------- -%% procs() +%% # procs/0 %% ---------------------------------------------------------- procs() -> ?I:procs(?APP). %% ---------------------------------------------------------- -%% # latest() +%% # latest/0 %% ---------------------------------------------------------- latest() -> ?I:latest(modules()). %% ---------------------------------------------------------- -%% # nl() +%% # nl/0 %% ---------------------------------------------------------- nl() -> lists:foreach(fun(M) -> abcast = c:nl(M) end, modules()). %% ---------------------------------------------------------- -%% # pp(Bin) +%% # pp/1 %% %% Description: Pretty-print a message binary. %% ---------------------------------------------------------- @@ -317,7 +327,7 @@ ppp({Field, Value}) -> io:format(": ~-22s : ~p~n", [Field, Value]). %% ---------------------------------------------------------- -%% # subscriptions() +%% # subscriptions/0 %% %% Returns a list of {SvcName, Pid}. %% ---------------------------------------------------------- @@ -326,7 +336,7 @@ subscriptions() -> diameter_service:subscriptions(). %% ---------------------------------------------------------- -%% # children() +%% # children/0 %% ---------------------------------------------------------- children() -> diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl index d10ee83ba4..ad5b3f9420 100644 --- a/lib/diameter/test/diameter_config_SUITE.erl +++ b/lib/diameter/test/diameter_config_SUITE.erl @@ -157,7 +157,7 @@ {length_errors, [[exit], [handle], [discard]], [[x]]}, - {reconnect_timer, + {connect_timer, [[3000]], [[infinity]]}, {watchdog_timer, diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl index 94b4967921..f43f111d20 100644 --- a/lib/diameter/test/diameter_event_SUITE.erl +++ b/lib/diameter/test/diameter_event_SUITE.erl @@ -107,29 +107,38 @@ start_server(Config) -> %% Connect with matching capabilities and expect the connection to %% come up. up(Config) -> - {Svc, Ref} = connect(Config, []), + {Svc, Ref} = connect(Config, [{connect_timer, 5000}, + {watchdog_timer, 15000}]), start = event(Svc), - {up, Ref, {_,_Caps}, _Config, #diameter_packet{}} = event(Svc), - {watchdog, Ref, _, {initial, okay}, _} = event(Svc). + {up, Ref, {TPid, Caps}, Cfg, #diameter_packet{}} = event(Svc), + {watchdog, Ref, _, {initial, okay}, _} = event(Svc), + %% Kill the transport process and see that the connection is + %% reestablished after a watchdog timeout, not after connect_timer + %% expiry. + exit(TPid, kill), + {down, Ref, {TPid, Caps}, Cfg} = event(Svc), + {watchdog, Ref, _, {okay, down}, _} = event(Svc), + {reconnect, Ref, _} = event(Svc, 10000, 20000). %% Connect with non-matching capabilities and expect CEA from the peer %% to indicate as much and then for the transport to be restarted -%% (after reconnect_timer). +%% (after connect_timer). down(Config) -> {Svc, Ref} = connect(Config, [{capabilities, [{'Acct-Application-Id', [?DICT_ACCT:id()]}]}, {applications, [?DICT_ACCT]}, - {reconnect_timer, 5000}]), + {connect_timer, 5000}, + {watchdog_timer, 20000}]), start = event(Svc), {closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{}}, _} = event(Svc), - {reconnect, Ref, _} = event(Svc). + {reconnect, Ref, _} = event(Svc, 4000, 10000). %% Connect with matching capabilities but have the server delay its %% CEA and cause the client to timeout. cea_timeout(Config) -> {Svc, Ref} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2}, - {reconnect_timer, 2*?SERVER_CAPX_TMO}]), + {connect_timer, 2*?SERVER_CAPX_TMO}]), start = event(Svc), {closed, Ref, {'CEA', timeout}, _} = event(Svc). @@ -165,6 +174,13 @@ uniq() -> event(Name) -> receive #diameter_event{service = Name, info = T} -> T end. +event(Name, TL, TH) -> + T0 = now(), + Event = event(Name), + DT = timer:now_diff(now(), T0) div 1000, + {true, true, DT, Event} = {TL < DT, DT < TH, DT, Event}, + Event. + start_service(Name, Opts) -> diameter:start_service(Name, [{monitor, self()} | Opts]). diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl index 9408fae62c..fcffa69c24 100644 --- a/lib/diameter/test/diameter_transport_SUITE.erl +++ b/lib/diameter/test/diameter_transport_SUITE.erl @@ -194,7 +194,7 @@ reconnect({connect, Ref}) -> true = diameter:subscribe(SvcName), ok = start_service(SvcName), [{{_, _, LRef}, Pid}] = diameter_reg:wait({?MODULE, Ref, '_'}), - CRef = ?util:connect(SvcName, tcp, LRef, [{reconnect_timer, 2000}, + CRef = ?util:connect(SvcName, tcp, LRef, [{connect_timer, 2000}, {watchdog_timer, 6000}]), %% Tell partner to kill transport after seeing that there are no diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 560c2aed50..587ae08b3d 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,5 +18,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.7 +DIAMETER_VSN = 1.8 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) |