From 39d0e598f4349b484d4b5f8fffc8fa9c25530ea8 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 25 Oct 2011 16:39:03 +0200 Subject: diameter_dbg:log -> diameter_lib:log Should have been included in 5af64c7d57d83ce35bfd7b15ac3ce6ec7459fd73. --- lib/diameter/include/diameter_gen.hrl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index d037e1044a..13a6c462af 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -40,14 +40,14 @@ encode_avps(Name, Rec) -> list_to_binary(encode(Name, Rec)) catch throw: {?MODULE, Reason} -> - diameter_dbg:log({encode, error}, + diameter_lib:log({encode, error}, ?MODULE, ?LINE, {Reason, Name, Rec}), erlang:error(list_to_tuple(Reason ++ [Name])); error: Reason -> Stack = erlang:get_stacktrace(), - diameter_dbg:log({encode, failure}, + diameter_lib:log({encode, failure}, ?MODULE, ?LINE, {Reason, Name, Rec, Stack}), @@ -159,7 +159,7 @@ d_rc(Name, {Avps, {Rec, [] = Failed}}) -> {Rec, Avps, Failed} catch throw: {?MODULE, {AvpName, Reason}} -> - diameter_dbg:log({decode, error}, + diameter_lib:log({decode, error}, ?MODULE, ?LINE, {AvpName, Reason, Rec}), @@ -260,7 +260,7 @@ d(Name, Avp, {Avps, Acc}) -> %% respond sensibly to. Log the occurence for traceability, %% but the peer will also receive info in the resulting %% answer-message. - diameter_dbg:log({decode, failure}, + diameter_lib:log({decode, failure}, ?MODULE, ?LINE, {Reason, Avp, erlang:get_stacktrace()}), -- cgit v1.2.3 From e20bc73324e9e2e4fc5efedfb905bfe4b5c6664a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 12 Oct 2011 18:17:12 +0200 Subject: Minor tls_ack simplification We're already monitoring the transport process, no need to do so again. --- lib/diameter/src/base/diameter_peer_fsm.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 282fa2742f..bc7625025a 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -685,12 +685,11 @@ open(Pkt, SupportedApps, RCaps, {Type, IS}, #state{parent = Pid, %% and expect a reply when the handshake is complete. tls_ack(true, Type, IS, #state{transport = TPid} = S) -> Ref = make_ref(), - MRef = erlang:monitor(process, TPid), TPid ! {diameter, {tls, Ref, Type, IS == ?TLS}}, receive {diameter, {tls, Ref}} -> - erlang:demonitor(MRef, [flush]); - {'DOWN', MRef, process, _, _} = T -> + ok; + {'DOWN', _, process, TPid, _} = T -> close({tls_ack, T}, S) end; -- cgit v1.2.3 From 30db65a00da4b0105faa760856286b42f0cde3e8 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 12 Oct 2011 18:39:36 +0200 Subject: Add capabilities_cb transport option Value is a function that's applied to the transport reference and capabilities record after capabilities exchange. If a callback returns anything but 'ok' then the connection is closed. In the case of an incoming CER, the callback can return a result code with which to answer. Multiple callbacks can be specified and are applied until either all return 'ok' or one doesn't. Also, include Origin-State-Id in answers where it was previously omitted. --- lib/diameter/src/base/diameter_capx.erl | 4 - lib/diameter/src/base/diameter_peer_fsm.erl | 156 ++++++++++++++++++++-------- 2 files changed, 110 insertions(+), 50 deletions(-) (limited to 'lib') diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 138e76411e..293eb0c196 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -224,10 +224,6 @@ rCER(CER, #diameter_service{capabilities = LCaps} = Svc) -> RCaps, CEA#diameter_base_CEA{'Result-Code' = ?SUCCESS})}. -%% TODO: 5.3 of RFC 3588 says we MUST return DIAMETER_NO_COMMON_APPLICATION -%% in the CEA and SHOULD disconnect the transport. However, we have -%% no way to guarantee the send before disconnecting. - build_CEA([], _, _, CEA) -> CEA#diameter_base_CEA{'Result-Code' = ?NOAPP}; diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index bc7625025a..3f1610b325 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -142,9 +142,12 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({WPid, {M, _} = T, Opts, #diameter_service{capabilities = Caps} = Svc0}) -> +i({WPid, T, Opts, #diameter_service{capabilities = Caps} = Svc0}) -> putr(dwa, dwa(Caps)), - {ok, TPid, Svc} = start_transport(T, Opts, Svc0), + {M, Ref} = T, + {[Ts], Rest} = proplists:split(Opts, [capabilities_cb]), + putr(capabilities_cb, {Ref, [F || {_,F} <- Ts]}), + {ok, TPid, Svc} = start_transport(T, Rest, Svc0), erlang:monitor(process, TPid), erlang:monitor(process, WPid), #state{parent = WPid, @@ -198,10 +201,13 @@ handle_info(T, #state{} = State) -> ?LOG(stop, T), x(T, State) catch - throw: {?MODULE, Tag, Reason} -> + {?MODULE, Tag, Reason} -> ?LOG(Tag, {Reason, T}), {stop, {shutdown, Reason}, State} end. +%% The form of the exception caught here is historical. It's +%% significant that it's not a 2-tuple, as in ?FAILURE(Reason), +%% since these are caught elsewhere. x(Reason, #state{} = S) -> close_wd(Reason, S), @@ -226,6 +232,9 @@ putr(Key, Val) -> getr(Key) -> get({?MODULE, Key}). +eraser(Key) -> + erase({?MODULE, Key}). + %% transition/2 %% Connection to peer. @@ -467,13 +476,13 @@ send_answer(Type, ReqPkt, #state{transport = TPid} = S) -> transport_data = TD} = ReqPkt, - {Answer, PostF} = build_answer(Type, V, ReqPkt, S), + {Msg, PostF} = build_answer(Type, V, ReqPkt, S), Pkt = #diameter_packet{header = #diameter_header{version = V, end_to_end_id = Eid, hop_by_hop_id = Hid, is_proxiable = P}, - msg = Answer, + msg = Msg, transport_data = TD}, send(TPid, diameter_codec:encode(?BASE, Pkt)), @@ -494,27 +503,31 @@ build_answer('CER', = Pkt, #state{service = Svc} = S) -> - #diameter_service{capabilities = #diameter_caps{origin_host = OH}} + {SupportedApps, RCaps, #diameter_base_CEA{'Result-Code' = RC, + 'Inband-Security-Id' = [IS]} + = CEA} + = recv_CER(CER, S), + + #diameter_service{capabilities = LCaps} = Svc, - {SupportedApps, - #diameter_caps{origin_host = DH} = RCaps, - #diameter_base_CEA{'Result-Code' = RC} - = CEA} - = recv_CER(CER, S), + #diameter_caps{origin_host = {OH, DH}} + = Caps + = capz(LCaps, RCaps), try 2001 == RC %% DIAMETER_SUCCESS - orelse ?THROW({sent_CEA, RC}), + orelse ?THROW({result_code, RC}), register_everywhere({?MODULE, connection, OH, DH}) - orelse ?THROW({election_lost, 4003}), - #diameter_base_CEA{'Inband-Security-Id' = [IS]} - = CEA, - {CEA, [fun open/5, Pkt, SupportedApps, RCaps, {accept, IS}]} + orelse ?THROW({result_code, 4003}), %% DIAMETER_ELECTION_LOST + caps_cb(Caps) + of + ok -> {CEA, [fun open/5, Pkt, SupportedApps, Caps, {accept, IS}]} catch - ?FAILURE({Reason, RC}) -> - {answer('CER', S) ++ [{'Result-Code', RC}], - [fun close/2, {'CER', Reason, DH}]} + ?FAILURE(discard = T) -> + close({'CER', T, DH}, S); + ?FAILURE({result_code, N}) -> + {answer_message(cea(S), N), [fun close/2, {'CER', N, DH}]} end; %% The error checks below are similar to those in diameter_service for @@ -522,18 +535,40 @@ build_answer('CER', build_answer(Type, V, #diameter_packet{header = H, errors = Es} = Pkt, S) -> FailedAvp = failed_avp([A || {_,A} <- Es]), - Ans = answer(answer(Type, S), V, H, Es), - {set(Ans, FailedAvp), if 'CER' == Type -> + Msg = answer_message(answer(Type, S), rc(V, H, Es)), + {set(Msg, FailedAvp), if 'CER' == Type -> [fun close/2, {Type, V, Pkt}]; true -> ok end}. +cea(S) -> + answer('CER', S). + +%% answer_message/2 + +answer_message([_ | Avps], RC) + when 3000 =< RC, RC < 4000 -> + ['answer-message', {'Result-Code', RC} + | lists:filter(fun is_origin/1, Avps)]; + +answer_message(Msg, RC) -> + Msg ++ [{'Result-Code', RC}]. + +is_origin({N, _}) -> + N == 'Origin-Host' + orelse N == 'Origin-Realm' + orelse N == 'Origin-State-Id'. + +%% failed_avp/1 + failed_avp([] = No) -> No; failed_avp(Avps) -> [{'Failed-AVP', [[{'AVP', Avps}]]}]. +%% set/2 + set(Ans, []) -> Ans; set(['answer-message' | _] = Ans, FailedAvp) -> @@ -541,18 +576,22 @@ set(['answer-message' | _] = Ans, FailedAvp) -> set([_|_] = Ans, FailedAvp) -> Ans ++ FailedAvp. -answer([_, OH, OR | _], _, #diameter_header{is_error = true}, _) -> - ['answer-message', OH, OR, {'Result-Code', 3008}]; +%% rc/3 -answer([_, OH, OR | _], _, _, [Bs|_]) +rc(_, #diameter_header{is_error = true}, _) -> + 3008; %% DIAMETER_INVALID_HDR_BITS + +rc(_, _, [Bs|_]) when is_bitstring(Bs) -> - ['answer-message', OH, OR, {'Result-Code', 3009}]; + 3009; %% DIAMETER_INVALID_HDR_BITS + +rc(?DIAMETER_VERSION, _, Es) -> + rc(Es); -answer(Ans, ?DIAMETER_VERSION, _, Es) -> - Ans ++ [{'Result-Code', rc(Es)}]; +rc(_, _, _) -> + 5011. %% DIAMETER_UNSUPPORTED_VERSION -answer(Ans, _, _, _) -> - Ans ++ [{'Result-Code', 5011}]. %% DIAMETER_UNSUPPORTED_VERSION +%% rc/1 rc([]) -> 2001; %% DIAMETER_SUCCESS @@ -595,12 +634,14 @@ a('CER', #diameter_caps{vendor_id = Vid, origin_host = Host, origin_realm = Realm, host_ip_address = Addrs, - product_name = Name}) -> + product_name = Name, + origin_state_id = OSI}) -> ['CEA', {'Origin-Host', Host}, {'Origin-Realm', Realm}, {'Host-IP-Address', Addrs}, {'Vendor-Id', Vid}, - {'Product-Name', Name}]; + {'Product-Name', Name}, + {'Origin-State-Id', OSI}]; a('DPR', #diameter_caps{origin_host = Host, origin_realm = Realm}) -> @@ -631,11 +672,11 @@ handle_CEA(#diameter_packet{header = #diameter_header{version = V}, [] == Errors orelse close({errors, Errors}, S), - {SApps, [IS], #diameter_caps{origin_host = DH} = RCaps} - = recv_CEA(CEA, S), + {SApps, [IS], RCaps} = recv_CEA(CEA, S), - #diameter_caps{origin_host = OH} - = LCaps, + #diameter_caps{origin_host = {OH, DH}} + = Caps + = capz(LCaps, RCaps), %% Ensure that we don't already have a connection to the peer in %% question. This isn't the peer election of 3588 except in the @@ -646,7 +687,11 @@ handle_CEA(#diameter_packet{header = #diameter_header{version = V}, register_everywhere({?MODULE, connection, OH, DH}) orelse close({'CEA', DH}, S), - open(DPkt, SApps, RCaps, {connect, IS}, S). + try caps_cb(Caps) of + ok -> open(DPkt, SApps, Caps, {connect, IS}, S) + catch + ?FAILURE(Reason) -> close(Reason, S) + end. %% recv_CEA/2 @@ -664,20 +709,39 @@ recv_CEA(CEA, #state{service = Svc} = S) -> close({'CEA', Reason}, S) end. +%% caps_cb/1 + +caps_cb(Caps) -> + {Ref, Ts} = eraser(capabilities_cb), + ccb(Ts, [Ref, Caps]). + +ccb([], _) -> + ok; +ccb([F | Rest], T) -> + case diameter_lib:eval([F|T]) of + ok -> + ccb(Rest, T); + Res -> + ?THROW({{capabilities_cb, F}, rejected(Res)}) + end. + +rejected({result_code, N} = T) + when 1000 =< N, N < 6000 -> + T; +rejected(discard = T) -> + T; +rejected(unknown) -> + {result_code, 3010}. %% DIAMETER_UNKNOWN_PEER + %% open/5 -open(Pkt, SupportedApps, RCaps, {Type, IS}, #state{parent = Pid, - service = Svc} - = S) -> - #diameter_service{capabilities = #diameter_caps{origin_host = OH, - inband_security_id = LS} - = LCaps} - = Svc, - #diameter_caps{origin_host = DH} - = RCaps, +open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> + #diameter_caps{origin_host = {_,_} = H, + inband_security_id = {LS,_}} + = Caps, tls_ack(lists:member(?TLS, LS), Type, IS, S), - Pid ! {open, self(), {OH,DH}, {capz(LCaps, RCaps), SupportedApps, Pkt}}, + Pid ! {open, self(), H, {Caps, SupportedApps, Pkt}}, S#state{state = 'Open'}. -- cgit v1.2.3 From a138f8420ed0386c6200899254190600bf121f97 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 25 Oct 2011 17:16:52 +0200 Subject: Use trivial capabilities callback in traffic suite --- lib/diameter/test/diameter_traffic_SUITE.erl | 6 +++++- lib/diameter/test/diameter_util.erl | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 522b00e781..55c5fc7c54 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -246,7 +246,7 @@ start_services(_Config) -> ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)). add_transports(Config) -> - LRef = ?util:listen(?SERVER, tcp), + LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx/2}]), CRef = ?util:connect(?CLIENT, tcp, LRef), ?util:write_priv(Config, "transport", {LRef, CRef}). @@ -261,6 +261,10 @@ stop_services(_Config) -> stop(_Config) -> ok = diameter:stop(). +capx(_, #diameter_caps{origin_host = {OH,DH}}) -> + io:format("connection: ~p -> ~p~n", [DH,OH]), + ok. + %% =========================================================================== %% Ensure that result codes have the expected values. diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 3fe8ea5363..6b1dc1f0c9 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -33,8 +33,8 @@ %% diameter-specific -export([lport/2, lport/3, - listen/2, - connect/3, + listen/2, listen/3, + connect/3, connect/4, disconnect/4]). %% common_test-specific @@ -254,22 +254,28 @@ lp(M, Ref, T) -> end. %% --------------------------------------------------------------------------- -%% listen/2 +%% listen/2-3 %% %% Add a listening transport on the loopback address and a free port. listen(SvcName, Prot) -> - add_transport(SvcName, {listen, opts(Prot, listen)}). + listen(SvcName, Prot, []). + +listen(SvcName, Prot, Opts) -> + add_transport(SvcName, {listen, opts(Prot, listen) ++ Opts}). %% --------------------------------------------------------------------------- -%% connect/3 +%% connect/2-3 %% %% Add a connecting transport on and connect to a listening transport %% with the specified reference. connect(Client, Prot, LRef) -> + connect(Client, Prot, LRef, []). + +connect(Client, Prot, LRef, Opts) -> [PortNr] = lport(Prot, LRef, 20), - Ref = add_transport(Client, {connect, opts(Prot, PortNr)}), + Ref = add_transport(Client, {connect, opts(Prot, PortNr) ++ Opts}), true = diameter:subscribe(Client), ok = receive {diameter_event, Client, {up, Ref, _, _, _}} -> ok -- cgit v1.2.3 From d415d9cd2c1adba28ce901ea1dd382cca96ae06c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 25 Oct 2011 19:26:13 +0200 Subject: Send events for connection establishment failure If a peer fsm process exits then the exit reason is received by the service process in a 'DOWN' message. If the reason is the one generated by diameter_peer_fsm:close/2, which is called to signal a non-transport failure before the completion of capabilities exchange (eg. receiving an unsuccessful CEA), then an event is sent to any subscribers. Also, tweak capabilities_cb return values for more informative event data. --- lib/diameter/src/base/diameter_capx.erl | 25 +--- lib/diameter/src/base/diameter_codec.erl | 22 +--- lib/diameter/src/base/diameter_peer_fsm.erl | 197 +++++++++++++++++----------- lib/diameter/src/base/diameter_service.erl | 42 +++--- 4 files changed, 144 insertions(+), 142 deletions(-) (limited to 'lib') diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 293eb0c196..842a9e6103 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -57,9 +57,9 @@ -include("diameter_types.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --define(SUCCESS, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_SUCCESS'). --define(NOAPP, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_NO_COMMON_APPLICATION'). --define(NOSECURITY, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_NO_COMMON_SECURITY'). +-define(SUCCESS, 2001). %% DIAMETER_SUCCESS +-define(NOAPP, 5010). %% DIAMETER_NO_COMMON_APPLICATION +-define(NOSECURITY, 5017). %% DIAMETER_NO_COMMON_SECURITY -define(NO_INBAND_SECURITY, 0). -define(TLS, 1). @@ -96,7 +96,7 @@ try_it([Fun | Args]) -> try apply(Fun, Args) of T -> {ok, T} catch - throw: ?FAILURE(Reason) -> {error, {Reason, Args}} + throw: ?FAILURE(Reason) -> {error, Reason} end. %% mk_caps/2 @@ -288,25 +288,12 @@ to_cea(CER, Field, CEA) -> %% rCEA/2 -rCEA(#diameter_base_CEA{'Result-Code' = RC} - = CEA, - #diameter_service{capabilities = LCaps} - = Svc) -> - RC == ?SUCCESS orelse ?THROW({'Result-Code', RC}), - +rCEA(CEA, #diameter_service{capabilities = LCaps} = Svc) -> RCaps = capx_to_caps(CEA), SApps = common_applications(LCaps, RCaps, Svc), - - [] == SApps andalso ?THROW(no_common_applications), - IS = common_security(LCaps, RCaps), - [] == IS andalso ?THROW(no_common_security), - - {SApps, IS, RCaps}; - -rCEA(CEA, _Svc) -> - ?THROW({invalid, CEA}). + {SApps, IS, RCaps}. %% capx_to_caps/1 diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index d88f42fb7c..fe1212b7e0 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -190,26 +190,13 @@ encode_avps(Avps) -> %% msg_header/3 -msg_header(Mod, MsgName, Header) -> - {Code, Flags, ApplId} = h(Mod, MsgName, Header), - {Code, p(Flags, Header), ApplId}. - -%% 6.2 of 3588 requires the same 'P' bit on an answer as on the -%% request. - -p(Flags, #diameter_header{is_request = true, - is_proxiable = P}) -> - Flags band (2#10110000 bor choose(P, 2#01000000, 0)); -p(Flags, _) -> - Flags. - -h(Mod, 'answer-message' = MsgName, Header) -> +msg_header(Mod, 'answer-message' = MsgName, Header) -> ?BASE = Mod, #diameter_header{cmd_code = Code} = Header, {_, Flags, ApplId} = ?BASE:msg_header(MsgName), {Code, Flags, ApplId}; -h(Mod, MsgName, _) -> +msg_header(Mod, MsgName, _) -> Mod:msg_header(MsgName). %% rec2msg/2 @@ -554,8 +541,3 @@ pack_avp(Code, Flags, Vid, Sz, Bin) -> pack_avp(Code, Flags, Sz, Bin) -> Length = Sz + 8, <>. - -%% =========================================================================== - -choose(true, X, _) -> X; -choose(false, _, X) -> X. diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 3f1610b325..d754c1bcc8 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -325,9 +325,10 @@ send_CER(#state{mode = {connect, Remote}, service = #diameter_service{capabilities = Caps}, transport = TPid} = S) -> - req_send_CER(Caps#diameter_caps.origin_host, Remote) + OH = Caps#diameter_caps.origin_host, + req_send_CER(OH, Remote) orelse - close(connected, S), + close({already_connected, Remote, Caps}, S), CER = build_CER(S), ?LOG(send, 'CER'), send(TPid, encode(CER)), @@ -469,19 +470,19 @@ handle_request(Type, #diameter_packet{} = Pkt, S) -> %% send_answer/3 send_answer(Type, ReqPkt, #state{transport = TPid} = S) -> - #diameter_packet{header = #diameter_header{version = V, - end_to_end_id = Eid, - hop_by_hop_id = Hid, - is_proxiable = P}, + #diameter_packet{header = H, transport_data = TD} = ReqPkt, - {Msg, PostF} = build_answer(Type, V, ReqPkt, S), + {Msg, PostF} = build_answer(Type, ReqPkt, S), - Pkt = #diameter_packet{header = #diameter_header{version = V, - end_to_end_id = Eid, - hop_by_hop_id = Hid, - is_proxiable = P}, + %% An answer message clears the R and T flags and retains the P + %% flag. The E flag is set at encode. + Pkt = #diameter_packet{header + = H#diameter_header{version = ?DIAMETER_VERSION, + is_request = false, + is_error = undefined, + is_retransmitted = false}, msg = Msg, transport_data = TD}, @@ -493,57 +494,79 @@ eval([F|A], S) -> eval(ok, S) -> S. -%% build_answer/4 +%% build_answer/3 build_answer('CER', - ?DIAMETER_VERSION, #diameter_packet{msg = CER, - header = #diameter_header{is_error = false}, + header = #diameter_header{version + = ?DIAMETER_VERSION, + is_error = false}, errors = []} = Pkt, - #state{service = Svc} - = S) -> + S) -> {SupportedApps, RCaps, #diameter_base_CEA{'Result-Code' = RC, - 'Inband-Security-Id' = [IS]} + 'Inband-Security-Id' = IS} = CEA} = recv_CER(CER, S), - #diameter_service{capabilities = LCaps} - = Svc, - #diameter_caps{origin_host = {OH, DH}} = Caps - = capz(LCaps, RCaps), + = capz(caps(S), RCaps), try 2001 == RC %% DIAMETER_SUCCESS - orelse ?THROW({result_code, RC}), + orelse ?THROW(RC), register_everywhere({?MODULE, connection, OH, DH}) - orelse ?THROW({result_code, 4003}), %% DIAMETER_ELECTION_LOST + orelse ?THROW(4003), %% DIAMETER_ELECTION_LOST caps_cb(Caps) of - ok -> {CEA, [fun open/5, Pkt, SupportedApps, Caps, {accept, IS}]} + N -> {cea(CEA, N), [fun open/5, Pkt, + SupportedApps, + Caps, + {accept, hd([_] = IS)}]} catch - ?FAILURE(discard = T) -> - close({'CER', T, DH}, S); - ?FAILURE({result_code, N}) -> - {answer_message(cea(S), N), [fun close/2, {'CER', N, DH}]} + ?FAILURE(Reason) -> + rejected(Reason, {'CER', Reason, Caps, Pkt}, S) end; %% The error checks below are similar to those in diameter_service for %% other messages. Should factor out the commonality. -build_answer(Type, V, #diameter_packet{header = H, errors = Es} = Pkt, S) -> - FailedAvp = failed_avp([A || {_,A} <- Es]), - Msg = answer_message(answer(Type, S), rc(V, H, Es)), - {set(Msg, FailedAvp), if 'CER' == Type -> - [fun close/2, {Type, V, Pkt}]; - true -> - ok - end}. +build_answer(Type, + #diameter_packet{header = H, + errors = Es} + = Pkt, + S) -> + RC = rc(H, Es), + {answer(Type, RC, Es, S), post(Type, RC, Pkt, S)}. + +cea(CEA, ok) -> + CEA; +cea(CEA, 2001) -> + CEA; +cea(CEA, RC) -> + CEA#diameter_base_CEA{'Result-Code' = RC}. + +post('CER' = T, RC, Pkt, S) -> + [fun close/2, {T, caps(S), {RC, Pkt}}]; +post(_, _, _, _) -> + ok. + +rejected({capabilities_cb, _F, Reason}, T, S) -> + rejected(Reason, T, S); -cea(S) -> - answer('CER', S). +rejected(discard, T, S) -> + close(T, S); +rejected({N, Es}, T, S) -> + {answer('CER', N, Es, S), [fun close/2, T]}; +rejected(N, T, S) -> + rejected({N, []}, T, S). + +answer(Type, RC, Es, S) -> + set(answer(Type, RC, S), failed_avp([A || {_,A} <- Es])). + +answer(Type, RC, S) -> + answer_message(answer(Type, S), RC). %% answer_message/2 @@ -576,19 +599,19 @@ set(['answer-message' | _] = Ans, FailedAvp) -> set([_|_] = Ans, FailedAvp) -> Ans ++ FailedAvp. -%% rc/3 +%% rc/2 -rc(_, #diameter_header{is_error = true}, _) -> +rc(#diameter_header{is_error = true}, _) -> 3008; %% DIAMETER_INVALID_HDR_BITS -rc(_, _, [Bs|_]) +rc(_, [Bs|_]) when is_bitstring(Bs) -> 3009; %% DIAMETER_INVALID_HDR_BITS -rc(?DIAMETER_VERSION, _, Es) -> +rc(#diameter_header{version = ?DIAMETER_VERSION}, Es) -> rc(Es); -rc(_, _, _) -> +rc(_, _) -> 5011. %% DIAMETER_UNSUPPORTED_VERSION %% rc/1 @@ -656,58 +679,68 @@ recv_CER(CER, #state{service = Svc}) -> %% handle_CEA/1 -handle_CEA(#diameter_packet{header = #diameter_header{version = V}, - bin = Bin} +handle_CEA(#diameter_packet{bin = Bin} = Pkt, #state{service = #diameter_service{capabilities = LCaps}} = S) when is_binary(Bin) -> ?LOG(recv, 'CEA'), - ?DIAMETER_VERSION == V orelse close({version, V}, S), - - #diameter_packet{msg = CEA, errors = Errors} + #diameter_packet{msg = CEA} = DPkt = diameter_codec:decode(?BASE, Pkt), - [] == Errors orelse close({errors, Errors}, S), - - {SApps, [IS], RCaps} = recv_CEA(CEA, S), + {SApps, IS, RCaps} = recv_CEA(DPkt, S), #diameter_caps{origin_host = {OH, DH}} = Caps = capz(LCaps, RCaps), + #diameter_base_CEA{'Result-Code' = RC} + = CEA, + %% Ensure that we don't already have a connection to the peer in %% question. This isn't the peer election of 3588 except in the %% sense that, since we don't know who we're talking to until we %% receive a CER/CEA, the first that arrives wins the right to a %% connection with the peer. - register_everywhere({?MODULE, connection, OH, DH}) - orelse close({'CEA', DH}, S), - - try caps_cb(Caps) of - ok -> open(DPkt, SApps, Caps, {connect, IS}, S) + try + 2001 == RC + orelse ?THROW(RC), + [] == SApps + andalso ?THROW(no_common_application), + [] == IS + andalso ?THROW(no_common_security), + register_everywhere({?MODULE, connection, OH, DH}) + orelse ?THROW(election_lost), + caps_cb(Caps) + of + _ -> open(DPkt, SApps, Caps, {connect, hd([_] = IS)}, S) catch - ?FAILURE(Reason) -> close(Reason, S) + ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt}, S) end. +%% Check more than the result code since the peer could send 2001 +%% regardless. %% recv_CEA/2 -recv_CEA(CEA, #state{service = Svc} = S) -> - case diameter_capx:recv_CEA(CEA, Svc) of - {ok, {_,_}} -> %% return from old code - close({'CEA', update}, S); - {ok, {[], _, _}} -> - close({'CEA', no_common_application}, S); - {ok, {_, [], _}} -> - close({'CEA', no_common_security}, S); - {ok, {_,_,_} = T} -> - T; - {error, Reason} -> - close({'CEA', Reason}, S) - end. +recv_CEA(#diameter_packet{header = #diameter_header{version + = ?DIAMETER_VERSION, + is_error = false}, + msg = CEA, + errors = []}, + #state{service = Svc}) -> + {ok, T} = diameter_capx:recv_CEA(CEA, Svc), + T; + +recv_CEA(Pkt, S) -> + close({'CEA', caps(S), Pkt}, S). + +caps(#diameter_service{capabilities = Caps}) -> + Caps; +caps(#state{service = Svc}) -> + caps(Svc). %% caps_cb/1 @@ -721,17 +754,21 @@ ccb([F | Rest], T) -> case diameter_lib:eval([F|T]) of ok -> ccb(Rest, T); + N when 2 == N div 1000 -> %% 2xxx Result-Code + N; Res -> - ?THROW({{capabilities_cb, F}, rejected(Res)}) + ?THROW({capabilities_cb, F, rejected(Res)}) end. +%% Note that returning 2xxx causes the capabilities exchange to be +%% accepted directly, without further callbacks. -rejected({result_code, N} = T) - when 1000 =< N, N < 6000 -> - T; rejected(discard = T) -> T; rejected(unknown) -> - {result_code, 3010}. %% DIAMETER_UNKNOWN_PEER + 3010; %% DIAMETER_UNKNOWN_PEER +rejected(N) + when is_integer(N) -> + N. %% open/5 @@ -740,26 +777,26 @@ open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> inband_security_id = {LS,_}} = Caps, - tls_ack(lists:member(?TLS, LS), Type, IS, S), + tls_ack(lists:member(?TLS, LS), Caps, Type, IS, S), Pid ! {open, self(), H, {Caps, SupportedApps, Pkt}}, S#state{state = 'Open'}. %% We've advertised TLS support: tell the transport the result %% and expect a reply when the handshake is complete. -tls_ack(true, Type, IS, #state{transport = TPid} = S) -> +tls_ack(true, Caps, Type, IS, #state{transport = TPid} = S) -> Ref = make_ref(), TPid ! {diameter, {tls, Ref, Type, IS == ?TLS}}, receive {diameter, {tls, Ref}} -> ok; - {'DOWN', _, process, TPid, _} = T -> - close({tls_ack, T}, S) + {'DOWN', _, process, TPid, Reason} -> + close({tls_ack, Reason, Caps}, S) end; %% Or not. Don't send anything to the transport so that transports %% not supporting TLS work as before without modification. -tls_ack(false, _, _, _) -> +tls_ack(false, _, _, _, _) -> ok. capz(#diameter_caps{} = L, #diameter_caps{} = R) -> diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 421e36ccf5..3ae6ed755f 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -983,7 +983,8 @@ peer_cb(MFA, Alias) -> connection_down(Pid, #state{peerT = PeerT, connT = ConnT} = S) -> - #peer{conn = TPid} + #peer{op_state = ?STATE_UP, %% assert + conn = TPid} = P = fetch(PeerT, Pid), @@ -993,6 +994,9 @@ connection_down(Pid, #state{peerT = PeerT, %% connection_down/3 +connection_down(#peer{op_state = ?STATE_DOWN}, _, S) -> + S; + connection_down(#peer{conn = TPid, op_state = ?STATE_UP} = P, @@ -1034,13 +1038,23 @@ down_conn(Id, Alias, TC, {SvcName, Apps}) -> %% Peer process has died. -peer_down(Pid, _Reason, #state{peerT = PeerT} = S) -> +peer_down(Pid, Reason, #state{peerT = PeerT} = S) -> P = fetch(PeerT, Pid), ets:delete_object(PeerT, P), + closed(Reason, P, S), restart(P,S), peer_down(P,S). -%% peer_down/2 +%% Send an event at connection establishment failure. +closed({shutdown, {close, _TPid, Reason}}, + #peer{op_state = ?STATE_DOWN, + ref = Ref, + type = Type, + options = Opts}, + #state{service_name = SvcName}) -> + send_event(SvcName, {closed, Ref, Reason, {type(Type), Opts}}); +closed(_, _, _) -> + ok. %% The peer has never come up ... peer_down(#peer{conn = B}, S) @@ -1048,27 +1062,9 @@ peer_down(#peer{conn = B}, S) S; %% ... or it has. -peer_down(#peer{ref = Ref, - conn = TPid, - type = Type, - options = Opts} - = P, - #state{service_name = SvcName, - connT = ConnT} - = S) -> - #conn{caps = Caps} - = C - = fetch(ConnT, TPid), +peer_down(#peer{conn = TPid} = P, #state{connT = ConnT} = S) -> + #conn{} = C = fetch(ConnT, TPid), ets:delete_object(ConnT, C), - try - pd(P,C,S) - after - send_event(SvcName, {closed, Ref, {TPid, Caps}, {type(Type), Opts}}) - end. - -pd(#peer{op_state = ?STATE_DOWN}, _, S) -> - S; -pd(#peer{op_state = ?STATE_UP} = P, C, S) -> connection_down(P,C,S). %% restart/2 -- cgit v1.2.3 From 020e92da676417dd1ab955d3ac81199972cace64 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 2 Nov 2011 16:42:02 +0100 Subject: Rename some functions plus comment tweak In diameter_service: make_packet -> make_request_packet make_header -> make_request_header make_reply_packet -> make_answer_packet --- lib/diameter/src/base/diameter_service.erl | 74 +++++++++++++++-------------- lib/diameter/src/base/diameter_watchdog.erl | 10 ++-- 2 files changed, 44 insertions(+), 40 deletions(-) (limited to 'lib') diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 3ae6ed755f..7adcf1c265 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -1255,11 +1255,11 @@ send_request({TPid, Caps, App}, Msg, Opts, Caller, SvcName) -> #diameter_app{module = ModX} = App, - Pkt = make_packet(Msg), + Pkt = make_request_packet(Msg), case cb(ModX, prepare_request, [Pkt, SvcName, {TPid, Caps}]) of {send, P} -> - send_request(make_packet(P, Pkt), + send_request(make_request_packet(P, Pkt), TPid, Caps, App, @@ -1274,70 +1274,73 @@ send_request({TPid, Caps, App}, Msg, Opts, Caller, SvcName) -> ?ERROR({invalid_return, prepare_request, App, T}) end. -%% make_packet/1 +%% make_request_packet/1 %% %% Turn an outgoing request as passed to call/4 into a diameter_packet %% record in preparation for a prepare_request callback. -make_packet(Bin) +make_request_packet(Bin) when is_binary(Bin) -> #diameter_packet{header = diameter_codec:decode_header(Bin), bin = Bin}; -make_packet(#diameter_packet{msg = [#diameter_header{} = Hdr | Avps]} = Pkt) -> - Pkt#diameter_packet{msg = [make_header(Hdr) | Avps]}; +make_request_packet(#diameter_packet{msg = [#diameter_header{} = Hdr | Avps]} + = Pkt) -> + Pkt#diameter_packet{msg = [make_request_header(Hdr) | Avps]}; -make_packet(#diameter_packet{header = Hdr} = Pkt) -> - Pkt#diameter_packet{header = make_header(Hdr)}; +make_request_packet(#diameter_packet{header = Hdr} = Pkt) -> + Pkt#diameter_packet{header = make_request_header(Hdr)}; -make_packet(Msg) -> - make_packet(#diameter_packet{msg = Msg}). +make_request_packet(Msg) -> + make_request_packet(#diameter_packet{msg = Msg}). -%% make_header/1 +%% make_request_header/1 -make_header(undefined) -> +make_request_header(undefined) -> Seq = diameter_session:sequence(), - make_header(#diameter_header{end_to_end_id = Seq, - hop_by_hop_id = Seq}); + make_request_header(#diameter_header{end_to_end_id = Seq, + hop_by_hop_id = Seq}); -make_header(#diameter_header{version = undefined} = Hdr) -> - make_header(Hdr#diameter_header{version = ?DIAMETER_VERSION}); +make_request_header(#diameter_header{version = undefined} = Hdr) -> + make_request_header(Hdr#diameter_header{version = ?DIAMETER_VERSION}); -make_header(#diameter_header{end_to_end_id = undefined} = H) -> +make_request_header(#diameter_header{end_to_end_id = undefined} = H) -> Seq = diameter_session:sequence(), - make_header(H#diameter_header{end_to_end_id = Seq}); + make_request_header(H#diameter_header{end_to_end_id = Seq}); -make_header(#diameter_header{hop_by_hop_id = undefined} = H) -> +make_request_header(#diameter_header{hop_by_hop_id = undefined} = H) -> Seq = diameter_session:sequence(), - make_header(H#diameter_header{hop_by_hop_id = Seq}); + make_request_header(H#diameter_header{hop_by_hop_id = Seq}); -make_header(#diameter_header{} = Hdr) -> +make_request_header(#diameter_header{} = Hdr) -> Hdr; -make_header(T) -> +make_request_header(T) -> ?ERROR({invalid_header, T}). -%% make_packet/2 +%% make_request_packet/2 %% %% Reconstruct a diameter_packet from the return value of %% prepare_request or prepare_retransmit callback. -make_packet(Bin, _) +make_request_packet(Bin, _) when is_binary(Bin) -> - make_packet(Bin); + make_request_packet(Bin); -make_packet(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _) -> +make_request_packet(#diameter_packet{msg = [#diameter_header{} | _]} + = Pkt, + _) -> Pkt; %% Returning a diameter_packet with no header from a prepare_request %% or prepare_retransmit callback retains the header passed into it. %% This is primarily so that the end to end and hop by hop identifiers %% are retained. -make_packet(#diameter_packet{header = Hdr} = Pkt, +make_request_packet(#diameter_packet{header = Hdr} = Pkt, #diameter_packet{header = Hdr0}) -> Pkt#diameter_packet{header = fold_record(Hdr0, Hdr)}; -make_packet(Msg, Pkt) -> +make_request_packet(Msg, Pkt) -> Pkt#diameter_packet{msg = Msg}. %% fold_record/2 @@ -1529,7 +1532,7 @@ retransmit({TPid, Caps, #diameter_app{alias = Alias} = App}, case cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]) of {send, P} -> - retransmit(make_packet(P, Pkt), TPid, Caps, Req, Timeout); + retransmit(make_request_packet(P, Pkt), TPid, Caps, Req, Timeout); {discard, Reason} -> ?THROW(Reason); discard -> @@ -1942,7 +1945,7 @@ reply(Msg, Dict, TPid, #diameter_packet{errors = Es, = ReqPkt) when [] == Es; is_record(hd(Msg), diameter_header) -> - Pkt = diameter_codec:encode(Dict, make_reply_packet(Msg, ReqPkt)), + Pkt = diameter_codec:encode(Dict, make_answer_packet(Msg, ReqPkt)), incr(send, Pkt, Dict, TPid), %% count result codes in sent answers send(TPid, Pkt#diameter_packet{transport_data = TD}); @@ -1953,18 +1956,19 @@ reply(Msg, Dict, TPid, #diameter_packet{errors = [H|_] = Es} = Pkt) -> TPid, Pkt#diameter_packet{errors = []}). -%% make_reply_packet/2 +%% make_answer_packet/2 %% Binaries and header/avp lists are sent as-is. -make_reply_packet(Bin, _) +make_answer_packet(Bin, _) when is_binary(Bin) -> #diameter_packet{bin = Bin}; -make_reply_packet([#diameter_header{} | _] = Msg, _) -> +make_answer_packet([#diameter_header{} | _] = Msg, _) -> #diameter_packet{msg = Msg}; %% Otherwise a reply message clears the R and T flags and retains the -%% P flag. The E flag will be set at encode. -make_reply_packet(Msg, #diameter_packet{header = ReqHdr}) -> +%% P flag. The E flag will be set at encode. 6.2 of 3588 requires the +%% same P flag on an answer as on the request. +make_answer_packet(Msg, #diameter_packet{header = ReqHdr}) -> Hdr = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, is_request = false, is_error = undefined, diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index b7c1491f4b..6dc53d9f31 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -179,11 +179,11 @@ transition({close, TPid, _Reason}, #watchdog{transport = TPid}) -> %% state okay as the result of the Peer State Machine reaching the %% Open state. %% -%% If we're an acceptor then we may be resuming a connection that went -%% down in another acceptor process, in which case this is the -%% transition below, from down into reopen. That is, it's not until -%% we know the identity of the peer (ie. now) that we know that we're -%% in state down rather than initial. +%% If we're accepting then we may be resuming a connection that went +%% down in another watchdog process, in which case this is the +%% transition below, from down to reopen. That is, it's not until we +%% know the identity of the peer (ie. now) that we know that we're in +%% state down rather than initial. transition({open, TPid, Hosts, T} = Open, #watchdog{transport = TPid, -- cgit v1.2.3 From e0854b9ffed89fb8bd61e8b57a0e5e933906207b Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 4 Nov 2011 17:11:18 +0100 Subject: Accept any 2xxx result code in CEA --- lib/diameter/src/base/diameter_peer_fsm.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index d754c1bcc8..fae5d763dc 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -55,7 +55,8 @@ -define(NO_INBAND_SECURITY, 0). -define(TLS, 1). --define(LOOP_TIMEOUT, 2000). +%% A 2xxx series Result-Code. Not necessarily 2001. +-define(IS_SUCCESS(N), 2 == (N) div 1000). %% RFC 3588: %% @@ -706,7 +707,7 @@ handle_CEA(#diameter_packet{bin = Bin} %% connection with the peer. try - 2001 == RC + ?IS_SUCCESS(RC) orelse ?THROW(RC), [] == SApps andalso ?THROW(no_common_application), @@ -720,8 +721,11 @@ handle_CEA(#diameter_packet{bin = Bin} catch ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt}, S) end. -%% Check more than the result code since the peer could send 2001 -%% regardless. +%% Check more than the result code since the peer could send success +%% regardless. If not 2001 then a peer_up callback could do anything +%% required. It's not unimaginable that a peer agreeing to TLS after +%% capabilities exchange could send DIAMETER_LIMITED_SUCCESS = 2002, +%% even if this isn't required by RFC 3588. %% recv_CEA/2 @@ -754,7 +758,7 @@ ccb([F | Rest], T) -> case diameter_lib:eval([F|T]) of ok -> ccb(Rest, T); - N when 2 == N div 1000 -> %% 2xxx Result-Code + N when ?IS_SUCCESS(N) -> %% 2xxx result code: accept immediately N; Res -> ?THROW({capabilities_cb, F, rejected(Res)}) -- cgit v1.2.3 From e16e495823db98b718ee43ef043536f40e3a86df Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 1 Nov 2011 19:42:25 +0100 Subject: Add capx suite Tests error handling at capabilities exchange. --- lib/diameter/test/diameter_capx_SUITE.erl | 432 ++++++++++++++++++++++++++++++ lib/diameter/test/modules.mk | 1 + 2 files changed, 433 insertions(+) create mode 100644 lib/diameter/test/diameter_capx_SUITE.erl (limited to 'lib') diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl new file mode 100644 index 0000000000..e6b1558bf6 --- /dev/null +++ b/lib/diameter/test/diameter_capx_SUITE.erl @@ -0,0 +1,432 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Tests of capabilities exchange between Diameter nodes. In +%% particular, of error and event handling. +%% + +-module(diameter_capx_SUITE). + +-export([suite/0, + all/0, + groups/0, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2]). + +%% testcases +-export([start/1, + start_services/1, + add_listeners/1, + s_no_common_application/1, + c_no_common_application/1, + s_no_common_security/1, + c_no_common_security/1, + s_unknown_peer/1, + c_unknown_peer/1, + s_unable/1, + c_unable/1, + s_client_reject/1, + c_client_reject/1, + remove_listeners/1, + stop_services/1, + stop/1]). + +%% diameter callbacks +-export([peer_up/4, + peer_down/4]). + +-include("diameter.hrl"). +-include("diameter_gen_base_rfc3588.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(CLIENT, client). +-define(SERVER, server). + +-define(ADDR, {127,0,0,1}). + +-define(REALM, "erlang.org"). +-define(HOST(Name), Name ++ "." ++ ?REALM). + +%% Config for diameter:start_service/2. +-define(SERVICE(Name), + [{'Origin-Realm', ?REALM}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {'Acct-Application-Id', [?DIAMETER_APP_ID_ACCOUNTING]} + | [{application, [{alias, A}, + {dictionary, D}, + {module, [?MODULE, A]}]} + || {A,D} <- [{common, ?DIAMETER_DICT_COMMON}, + {accounting, ?DIAMETER_DICT_ACCOUNTING}]]]). + +-define(A, list_to_atom). +-define(L, atom_to_list). + +-define(event, #diameter_event). +-define(caps, #diameter_caps). +-define(packet, #diameter_packet). + +-define(cea, #diameter_base_CEA). +-define(answer_message, #'diameter_base_answer-message'). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 10}}]. + +all() -> + [start, start_services, add_listeners + | [{group, N} || {N, _, _} <- groups()]] + ++ [remove_listeners, stop_services, stop]. + +groups() -> + Ts = testcases(), + [{grp(P), P, Ts} || P <- [[], [parallel]]]. + +grp([]) -> + sequential; +grp([parallel = P]) -> + P. + +init_per_group(_Name, Config) -> + Config. + +end_per_group(_, _) -> + ok. + +%% Generate a unique hostname for each testcase so that watchdogs +%% don't prevent a connection from being brought up immediately. +init_per_testcase(Name, Config) -> + Uniq = ["." ++ integer_to_list(N) || N <- tuple_to_list(now())], + [{host, lists:flatten([?L(Name) | Uniq])} | Config]. + +end_per_testcase(N, _) + when N == start; + N == start_services; + N == add_listeners; + N == remove_listeners; + N == stop_services; + N == stop -> + ok; +end_per_testcase(Name, Config) -> + CRef = ?util:read_priv(Config, Name), + ok = diameter:remove_transport(?CLIENT, CRef). + +%% Testcases all come in two flavours, client and server. +testcases() -> + lists:flatmap(fun tc/1, tc()). + +tc(Name) -> + [?A([C,$_|?L(Name)]) || C <- "cs"]. + +tc() -> + [no_common_application, + no_common_security, + unknown_peer, + unable, + client_reject]. + +%% =========================================================================== +%% start/stop testcases + +start(_Config) -> + ok = diameter:start(). + +start_services(_Config) -> + ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)). + +%% One server that responds only to base accounting, one that responds +%% to both this and the common application. Share a common service just +%% to simplify config, and because we can. +add_listeners(Config) -> + Acct = listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST("acct-srv")}, + {'Auth-Application-Id', []}]}, + {applications, [accounting]}, + {capabilities_cb, [fun server_capx/3, acct]}]), + Base = listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST("base-srv")}]}, + {capabilities_cb, [fun server_capx/3, base]}]), + ?util:write_priv(Config, ?MODULE, {Base, Acct}). %% lref/2 reads + +remove_listeners(_Config) -> + ok = diameter:remove_transport(?SERVER, true). + +stop_services(_Config) -> + ok = diameter:stop_service(?CLIENT), + ok = diameter:stop_service(?SERVER). + +stop(_Config) -> + ok = diameter:stop(). + +%% =========================================================================== +%% All the testcases come in pairs, one for receiving an event on the +%% client side, one on the server side. Note that testcases will +%% receive events resulting from other testcases when running in +%% parallel since the events are per service. The unique client +%% Origin-Host for each testcase plus transport references are used to +%% ensure that only the relevant event is extracted from the mailbox. +%% Don't bother extracting events that aren't relevant. + +%% ==================== +%% Ask the accounting server to speak the common application and expect +%% DIAMETER_NO_COMMON_APPLICATION = 5010. + +s_no_common_application(Config) -> + server_closed(Config, fun no_common_application/1, 5010). + +c_no_common_application(Config) -> + client_closed(Config, "acct-srv", fun no_common_application/1, 5010). + +no_common_application(Config) -> + connect(Config, acct, [{capabilities, [{'Acct-Application-Id', []}]}, + {applications, [common]}]). + +%% ==================== +%% Ask the base server to speak accounting with an unknown security +%% method and expect DIAMETER_NO_COMMON_SECURITY = 5017. + +s_no_common_security(Config) -> + server_closed(Config, fun no_common_security/1, 5017). + +c_no_common_security(Config) -> + client_closed(Config, "base-srv", fun no_common_security/1, 5017). + +no_common_security(Config) -> + connect(Config, base, [{capabilities, [{'Acct-Application-Id', []}, + {'Inband-Security-Id', [17, 18]}]}, + {applications, [common]}]). + +%% ==================== +%% Have the base server reject a decent CER with the protocol error +%% DIAMETER_UNKNOWN_PEER = 3010. + +s_unknown_peer(Config) -> + server_reject(Config, fun base/1, 3010). + +c_unknown_peer(Config) -> + true = diameter:subscribe(?CLIENT), + OH = ?HOST("base-srv"), + + {CRef, _} = base(Config), + + {'CEA', ?caps{}, + ?packet{msg = ?answer_message{'Origin-Host' = OH, + 'Result-Code' = 3010}}} + = client_recv(CRef). + +base(Config) -> + connect(Config, base, []). + +%% ==================== +%% Have the base server reject a decent CER with the non-protocol +%% error DIAMETER_UNABLE_TO_COMPLY = 5012. + +s_unable(Config) -> + server_reject(Config, fun base/1, 5012). + +c_unable(Config) -> + client_closed(Config, "base-srv", fun base/1, 5012). + +%% ==================== +%% Have the client reject a decent CEA. + +s_client_reject(Config) -> + true = diameter:subscribe(?SERVER), + OH = host(Config), + + {_, LRef} = client_reject(Config), + + receive + ?event{service = ?SERVER, + info = {up, LRef, + {_, ?caps{origin_host = {_, OH}}}, + {listen, _}, + ?packet{}}} + = Info -> + Info + after 2000 -> + fail({LRef, OH}) + end. + +c_client_reject(Config) -> + true = diameter:subscribe(?CLIENT), + OH = ?HOST("acct-srv"), + + {CRef, _} = client_reject(Config), + + {'CEA', {capabilities_cb, _, discard}, + ?caps{origin_host = {_, OH}}, + ?packet{msg = ?cea{'Result-Code' = 2001}}} + = client_recv(CRef). + +client_reject(Config) -> + connect(Config, acct, [{capabilities_cb, fun client_capx/2}]). + +%% =========================================================================== + +%% server_closed/3 + +server_closed(Config, F, RC) -> + true = diameter:subscribe(?SERVER), + OH = host(Config), + + {_, LRef} = F(Config), + + receive + ?event{service = ?SERVER, + info = {closed, LRef, + {'CER', RC, + ?caps{origin_host = {_, OH}}, + ?packet{}} + = Reason, + {listen, _}}} -> + Reason + after 2000 -> + fail({LRef, OH}) + end. + +%% server_reject/3 + +server_reject(Config, F, RC) -> + true = diameter:subscribe(?SERVER), + OH = host(Config), + + {_, LRef} = F(Config), + + receive + ?event{service = ?SERVER, + info = {closed, LRef, + {'CER', {capabilities_cb, _, RC}, + ?caps{origin_host = {_, OH}}, + ?packet{}} + = Reason, + {listen, _}}} -> + Reason + after 2000 -> + fail({LRef, OH}) + end. + +%% cliient_closed/4 + +client_closed(Config, Host, F, RC) -> + true = diameter:subscribe(?CLIENT), + OH = ?HOST(Host), + + {CRef, _} = F(Config), + + {'CEA', RC, ?caps{origin_host = {_, OH}}, ?packet{}} + = client_recv(CRef). + +%% client_recv/1 + +client_recv(CRef) -> + receive + ?event{service = ?CLIENT, + info = {closed, CRef, Reason, {connect, _}}} -> + Reason + after 2000 -> + fail(CRef) + end. + +%% server_capx/3 + +server_capx(_, ?caps{origin_host = {_, [_,$_|"unknown_peer." ++ _]}}, _) -> + unknown; + +server_capx(_, ?caps{origin_host = {_, [_,$_|"unable." ++ _]}}, _) -> + 5012; %% DIAMETER_UNABLE_TO_COMPLY + +server_capx(_, ?caps{origin_host = {OH,DH}}, _) -> + io:format("connection: ~p -> ~p~n", [DH,OH]), + ok. + +%% client_capx/2 + +client_capx(_, ?caps{origin_host = {[_,$_|"client_reject." ++ _], _}}) -> + discard. + +%% =========================================================================== + +fail(T) -> + erlang:error({T, process_info(self(), messages)}). + +host(Config) -> + {_, H} = lists:keyfind(host, 1, Config), + ?HOST(H). + +listen(Name, Opts) -> + ?util:listen(Name, tcp, Opts). + +connect(Config, T, Opts) -> + {_, H} = lists:keyfind(host, 1, Config), + LRef = lref(Config, T), + CRef = connect(LRef, [{capabilities, [{'Origin-Host', ?HOST(H)}]} + | Opts]), + Name = lists:takewhile(fun(C) -> C /= $. end, H), + ?util:write_priv(Config, Name, CRef), %% end_per_testcase reads + {CRef, LRef}. + +connect(LRef, Opts) -> + [PortNr] = ?util:lport(tcp, LRef, 20), + {ok, CRef} = diameter:add_transport(?CLIENT, + {connect, opts(PortNr, Opts)}), + CRef. + +opts(PortNr, Opts) -> + [{transport_module, diameter_tcp}, + {transport_config, [{raddr, ?ADDR}, + {rport, PortNr}, + {ip, ?ADDR}, + {port, 0}]} + | Opts]. + +lref(Config, T) -> + case ?util:read_priv(Config, ?MODULE) of + {LRef, _} when T == base -> + LRef; + {_, LRef} when T == acct -> + LRef + end. + +%% =========================================================================== +%% diameter callbacks + +peer_up(?SERVER, + {_, ?caps{origin_host = {"acct-srv." ++ _, + [_,$_|"client_reject." ++ _]}}}, + State, + _) -> + State. + +peer_down(?SERVER, + {_, ?caps{origin_host = {"acct-srv." ++ _, + [_,$_|"client_reject." ++ _]}}}, + State, + _) -> + State. diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 75fdd0bd1d..f88258c232 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -33,6 +33,7 @@ MODULES = \ diameter_stats_SUITE \ diameter_watchdog_SUITE \ diameter_transport_SUITE \ + diameter_capx_SUITE \ diameter_traffic_SUITE \ diameter_relay_SUITE \ diameter_tls_SUITE \ -- cgit v1.2.3 From 7721c6ab406aaf29cf1b2d782c5baf65d2b0e2da Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 13 Oct 2011 01:34:07 +0200 Subject: Update documentation --- lib/diameter/doc/src/diameter.xml | 123 +++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 43c497f50a..2d8edb1301 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -431,7 +431,7 @@ eval(F) ->

-Evaluating an evaluable() E on an argument list A +Applying an evaluable() E to an argument list A is meant in the sense of eval([E|A]).

@@ -565,7 +565,7 @@ Pkt = #diameter_packet{}

-Reports that the RFC 3539 watchdog state machine has +The RFC 3539 watchdog state machine has transitioned into (up) or out of (down) the open state. If a diameter_packet record is present in an up tuple @@ -576,9 +576,9 @@ connectivity.

Note that a single up/down event for a given peer corresponds to -as many peer_up/down callbacks as there are Diameter -applications shared by the peer, as determined during capablilities -exchange. +as many peer_up/peer_down +callbacks as there are Diameter applications shared by the peer, +as determined during capablilities exchange. That is, the event communicates connectivity with the peer as a whole while the callbacks communicate connectivity with respect to individual Diameter applications.

@@ -597,12 +597,96 @@ transport connection with a peer following reconnect_timer or watchdog_timer expiry.

+{closed, Ref, Reason, Config} + + +Ref = transport_ref() +Config = {connect|listen, [transport_opt()]} + + +

+Capabilities exchange has failed. Reason can be one of +the following.

+ + + +{'CER', Result, Caps, Pkt} + + +Result = ResultCode | {capabilities_cb, CB, ResultCode|discard} +Caps = #diameter_caps{} +Pkt = #diameter_packet{} +ResultCode = integer() +CB = evaluable() + + +

+An incoming CER has been answered with the indicated result code or +discarded. +The capabilities record contains pairs of values for the the local +node and remote peer. +The packet record contains the CER in question. +In the case of rejection by a capabilities callback, the tuple +indicates the rejecting callback.

+
+ +{'CER', Caps, {ResultCode, Pkt}} + + +ResultCode = integer() +Caps = #diameter_caps{} +Pkt = #diameter_packet{} + + +

+An incoming CER contained errors and has been answered with the +indicated result code. +The capabilities record contains only values for the the local +node. +The packet record contains the CER in question.

+
+ +{'CEA', Result, Caps, Pkt} + + +Result = integer() | atom() | {capabilities_cb, CB, ResultCode|discard} +Caps = #diameter_caps{} +Pkt = #diameter_packet{} +ResultCode = integer() + + +

+An incoming CEA has been rejected for the indicated reason. +An integer-valued Result indicates the result code sent +by the peer. +The capabilities record contains pairs of values for the the local +node and remote peer. +The packet record contains the CEA in question. +In the case of rejection by a capabilities callback, the tuple +indicates the rejecting callback.

+
+ +{'CEA', Caps, Pkt} + + +Caps = #diameter_caps{} +Pkt = #diameter_packet{} + + +

+An incoming CER contained errors and has been rejected. +The capabilities record contains only values for the the local node. +The packet record contains the CEA in question.

+
+ +
+
+

For forward compatibility, a subscriber should be prepared to receive -diameter_event.info of forms other than those documented -above.

+info fields of forms other than the above.

@@ -706,6 +790,31 @@ not over SCTP as implemented by diameter_sctp(3).

+{capabilities_cb, evaluable()} + +

+A 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 reference (as returned by add_transport/2) and +diameter_caps record of the connection. +Returning ok accepts the connection. +Returning integer() causes an incoming +CER to be answered with the specified Result-Code. +Returning discard causes an incoming CER to +be discarded. +Returning unknown is equivalent to returning 3010, +DIAMETER_UNKNOWN_PEER. +Returning anything but ok or a 2xxx series result +code causes the transport connection to be broken.

+ +

+Multiple capabilities_cb options can be specified, in which +case the corresponding callbacks are applied until either all return +ok or one does not.

+
+ {watchdog_timer, TwInit} -- cgit v1.2.3