diff options
Diffstat (limited to 'lib/diameter/test')
-rw-r--r-- | lib/diameter/test/diameter_event_SUITE.erl | 182 | ||||
-rw-r--r-- | lib/diameter/test/diameter_failover_SUITE.erl | 141 | ||||
-rw-r--r-- | lib/diameter/test/diameter_traffic_SUITE.erl | 258 | ||||
-rw-r--r-- | lib/diameter/test/modules.mk | 3 |
4 files changed, 500 insertions, 84 deletions
diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl new file mode 100644 index 0000000000..7c1c76f22a --- /dev/null +++ b/lib/diameter/test/diameter_event_SUITE.erl @@ -0,0 +1,182 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. 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 events sent as a consequence of diameter:subscribe/1. +%% Watchdog events are dealt with more extensively in the watchdog +%% suite. +%% + +-module(diameter_event_SUITE). + +-export([suite/0, + all/0, + init_per_testcase/2, + end_per_testcase/2]). + +%% testcases +-export([start/1, + start_server/1, + up/1, + down/1, + cea_timeout/1, + stop/1]). + +-include("diameter.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(ADDR, {127,0,0,1}). +-define(REALM, "REALM"). + +-define(SERVER, "SERVER.SERVER-REALM"). +-define(CLIENT, "CLIENT.CLIENT-REALM"). + +-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). +-define(DICT_ACCT, ?DIAMETER_DICT_ACCOUNTING). + +-define(SERVER_CAPX_TMO, 6000). + +%% Config for diameter:start_service/2. +-define(SERVICE(Host, Dicts), + [{'Origin-Host', Host}, + {'Origin-Realm', realm(Host)}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Acct-Application-Id', [D:id() || D <- Dicts]} + | [{application, [{dictionary, D}, + {module, #diameter_callback{}}]} + || D <- Dicts]]). + +%% Diameter Result-Code's: +-define(NO_COMMON_APP, 5010). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [start, + start_server, + up, + down, + cea_timeout, + stop]. + +init_per_testcase(Name, Config) -> + [{name, Name} | Config]. + +end_per_testcase(_, _) -> + ok. + +%% =========================================================================== +%% start/stop testcases + +start(_Config) -> + ok = diameter:start(). + +start_server(Config) -> + diameter:subscribe(?SERVER), + ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER, [?DICT_COMMON])), + LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx_cb/2}, + {capx_timeout, ?SERVER_CAPX_TMO}]), + [PortNr] = ?util:lport(tcp, LRef, 20), + ?util:write_priv(Config, portnr, PortNr), + start = event(?SERVER). + +%% Connect with matching capabilities and expect the connection to +%% come up. +up(Config) -> + {Svc, Ref} = connect(Config, []), + start = event(Svc), + {up, Ref, {_,_Caps}, _Config, #diameter_packet{}} = event(Svc), + {watchdog, Ref, _, {initial, okay}, _} = event(Svc). + +%% 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). +down(Config) -> + {Svc, Ref} = connect(Config, [{capabilities, [{'Acct-Application-Id', + [?DICT_ACCT:id()]}]}, + {applications, [?DICT_ACCT]}, + {reconnect_timer, 5000}]), + start = event(Svc), + {watchdog, Ref, _, {initial, down}, _} = event(Svc), + {closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{}}, _} + = event(Svc), + {reconnect, Ref, _} = event(Svc). + +%% 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}]), + start = event(Svc), + {watchdog, Ref, _, {initial, down}, _} = event(Svc), + {closed, Ref, {'CEA', timeout}, _} = event(Svc). + +stop(_Config) -> + ok = diameter:stop(). + +%% ---------------------------------------- + +%% Keep the server from sending CEA until the client has timed out. +capx_cb(_, #diameter_caps{origin_host = {_, "cea_timeout-" ++ _}}) -> + receive after ?SERVER_CAPX_TMO -> ok end; + +%% Or not. +capx_cb(_, _Caps) -> + ok. + +%% ---------------------------------------- + +%% Use the testcase name to construct Origin-Host of the client so +%% that the server can match on it in capx_cb/2. +connect(Config, Opts) -> + Pre = atom_to_list(proplists:get_value(name, Config)), + Name = Pre ++ uniq() ++ ?CLIENT, + diameter:subscribe(Name), + ok = start_service(Name, ?SERVICE(Name, [?DICT_COMMON, ?DICT_ACCT])), + {ok, Ref} = diameter:add_transport(Name, opts(Config, Opts)), + {Name, Ref}. + +uniq() -> + {MS,S,US} = now(), + lists:flatten(io_lib:format("-~p-~p-~p-", [MS,S,US])). + +event(Name) -> + receive #diameter_event{service = Name, info = T} -> T end. + +start_service(Name, Opts) -> + diameter:start_service(Name, [{monitor, self()} | Opts]). + +opts(Config, Opts) -> + PortNr = ?util:read_priv(Config, portnr), + + {connect, [{transport_module, diameter_tcp}, + {transport_config, [{ip, ?ADDR}, {port, 0}, + {raddr, ?ADDR}, {rport, PortNr}]} + | Opts]}. + +realm(Host) -> + tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index ed31670031..bb820a8bf2 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_SUITE.erl @@ -18,19 +18,19 @@ %% %% -%% Tests of traffic between six Diameter nodes in three realms, +%% Tests of traffic between seven Diameter nodes in four realms, %% connected as follows. %% -%% ----- SERVER1.REALM2 -%% / -%% / ----- SERVER2.REALM2 -%% | / -%% CLIENT.REALM1 ------ SERVER3.REALM2 -%% | \ -%% | \ -%% \ ---- SERVER1.REALM3 -%% \ -%% ----- SERVER2.REALM3 +%% ----- SERVER1.REALM2 ----- +%% / \ +%% / ----- SERVER2.REALM2 ----- \ +%% | / \ | +%% CLIENT.REALM1 ------ SERVER3.REALM2 ------ CLIENT.REALM4 +%% | \ / | +%% | \ / | +%% \ ---- SERVER1.REALM3 ----- / +%% \ / +%% ----- SERVER2.REALM3 ----- %% -module(diameter_failover_SUITE). @@ -44,12 +44,16 @@ connect/1, send_ok/1, send_nok/1, + send_discard_1/1, + send_discard_2/1, stop_services/1, stop/1]). %% diameter callbacks -export([pick_peer/4, prepare_request/3, + prepare_retransmit/3, + handle_error/4, handle_answer/4, handle_request/3]). @@ -62,14 +66,18 @@ -define(ADDR, {127,0,0,1}). --define(CLIENT, "CLIENT.REALM1"). +-define(CLIENT1, "CLIENT.REALM1"). +-define(CLIENT2, "CLIENT.REALM4"). -define(SERVER1, "SERVER1.REALM2"). -define(SERVER2, "SERVER2.REALM2"). -define(SERVER3, "SERVER3.REALM2"). -define(SERVER4, "SERVER1.REALM3"). -define(SERVER5, "SERVER2.REALM3"). --define(SERVICES, [?CLIENT, ?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). +-define(IS_CLIENT(Svc), Svc == ?CLIENT1; Svc == ?CLIENT2). + +-define(CLIENTS, [?CLIENT1, ?CLIENT2]). +-define(SERVERS, [?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). -define(DICT_COMMON, ?DIAMETER_DICT_COMMON). @@ -77,26 +85,27 @@ -define(APP_ID, ?DICT_COMMON:id()). %% Config for diameter:start_service/2. --define(SERVICE(Host, Dict), +-define(SERVICE(Host), [{'Origin-Host', Host}, {'Origin-Realm', realm(Host)}, {'Host-IP-Address', [?ADDR]}, {'Vendor-Id', 12345}, {'Product-Name', "OTP/diameter"}, - {'Acct-Application-Id', [Dict:id()]}, + {'Acct-Application-Id', [?APP_ID]}, {application, [{alias, ?APP_ALIAS}, - {dictionary, Dict}, + {dictionary, ?DICT_COMMON}, {module, #diameter_callback {peer_up = false, peer_down = false, - handle_error = false, - prepare_retransmit = false, default = ?MODULE}}, {answer_errors, callback}]}]). -define(SUCCESS, 2001). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +%% Value of Termination-Cause determines client/server behaviour. +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_USER_MOVED'). +-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_SESSION_TIMEOUT'). %% =========================================================================== @@ -109,6 +118,8 @@ all() -> connect, send_ok, send_nok, + send_discard_1, + send_discard_2, stop_services, stop]. @@ -119,19 +130,20 @@ start(_Config) -> ok = diameter:start(). start_services(_Config) -> - S = [server(N, ?DICT_COMMON) || N <- tl(?SERVICES)], - - ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, ?DICT_COMMON)), + Servers = [server(N) || N <- ?SERVERS], + [] = [T || C <- ?CLIENTS, + T <- [diameter:start_service(C, ?SERVICE(C))], + T /= ok], - {save_config, [{?CLIENT, S}]}. + {save_config, Servers}. connect(Config) -> - {_, Conns} = proplists:get_value(saved_config, Config), + {start_services, Servers} = proplists:get_value(saved_config, Config), - lists:foreach(fun({CN,Ss}) -> connect(CN, Ss) end, Conns). + lists:foreach(fun(C) -> connect(C, Servers) end, ?CLIENTS). stop_services(_Config) -> - [] = [{H,T} || H <- ?SERVICES, + [] = [{H,T} || H <- ?CLIENTS ++ ?SERVERS, T <- [diameter:stop_service(H)], T /= ok]. @@ -140,8 +152,8 @@ stop(_Config) -> %% ---------------------------------------- -server(Name, Dict) -> - ok = diameter:start_service(Name, ?SERVICE(Name, Dict)), +server(Name) -> + ok = diameter:start_service(Name, ?SERVICE(Name)), {Name, ?util:listen(Name, tcp)}. connect(Name, Refs) -> @@ -153,30 +165,39 @@ connect(Name, Refs) -> %% Send an STR and expect success after SERVER3 answers after a couple %% of failovers. send_ok(_Config) -> - Req = ['STR', {'Destination-Realm', realm(?SERVER1)}, - {'Termination-Cause', ?LOGOUT}, - {'Auth-Application-Id', ?APP_ID}], + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), + 'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?APP_ID}, #diameter_base_STA{'Result-Code' = ?SUCCESS, 'Origin-Host' = ?SERVER3} - = call(Req, [{filter, realm}]). + = call(?CLIENT1, Req). %% Send an STR and expect failure when both servers fail. send_nok(_Config) -> - Req = ['STR', {'Destination-Realm', realm(?SERVER4)}, - {'Termination-Cause', ?LOGOUT}, - {'Auth-Application-Id', ?APP_ID}], - {error, failover} = call(Req, [{filter, realm}]). + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), + 'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?APP_ID}, + {failover, ?LOGOUT} = call(?CLIENT1, Req). + +%% Send an STR and have prepare_retransmit discard it. +send_discard_1(_Config) -> + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), + 'Termination-Cause' = ?TIMEOUT, + 'Auth-Application-Id' = ?APP_ID}, + {rejected, ?TIMEOUT} = call(?CLIENT2, Req). +send_discard_2(_Config) -> + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), + 'Termination-Cause' = ?MOVED, + 'Auth-Application-Id' = ?APP_ID}, + {discarded, ?MOVED} = call(?CLIENT2, Req). %% =========================================================================== realm(Host) -> tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). -call(Req, Opts) -> - diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). - -set([H|T], Vs) -> - [H | Vs ++ T]. +call(Svc, Req) -> + diameter:call(Svc, ?APP_ALIAS, Req, [{filter, realm}]). %% =========================================================================== %% diameter callbacks @@ -184,7 +205,8 @@ set([H|T], Vs) -> %% pick_peer/4 %% Choose a server other than SERVER3 or SERVER5 if possible. -pick_peer(Peers, _, ?CLIENT, _State) -> +pick_peer(Peers, _, Svc, _State) + when ?IS_CLIENT(Svc) -> case lists:partition(fun({_, #diameter_caps{origin_host = {_, OH}}}) -> OH /= ?SERVER3 andalso OH /= ?SERVER5 end, @@ -198,20 +220,41 @@ pick_peer(Peers, _, ?CLIENT, _State) -> %% prepare_request/3 -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) -> +prepare_request(Pkt, Svc, {_Ref, Caps}) + when ?IS_CLIENT(Svc) -> {send, prepare(Pkt, Caps)}. prepare(#diameter_packet{msg = Req}, Caps) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}]). + Req#diameter_base_STR{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = diameter:session_id(OH)}. + +%% prepare_retransmit/3 + +prepare_retransmit(#diameter_packet{header = H} = P, Svc, {_,_}) + when ?IS_CLIENT(Svc) -> + #diameter_header{is_retransmitted = true} = H, %% assert + prepare(P). + +prepare(#diameter_packet{msg = M} = P) -> + case M#diameter_base_STR.'Termination-Cause' of + ?LOGOUT -> {send, P}; + ?MOVED -> discard; + ?TIMEOUT -> {discard, rejected} + end. + +%% handle_error/4 + +handle_error(Reason, Req, _, _) -> + {Reason, Req#diameter_base_STR.'Termination-Cause'}. %% handle_answer/4 -handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> +handle_answer(Pkt, _Req, Svc, _Peer) + when ?IS_CLIENT(Svc) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. @@ -219,8 +262,8 @@ handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> %% Only SERVER3 actually answers. handle_request(Pkt, ?SERVER3, {_, Caps}) -> - #diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId, - 'Origin-Host' = ?CLIENT}} + #diameter_packet{header = #diameter_header{is_retransmitted = true}, + msg = #diameter_base_STR{'Session-Id' = SId}} = Pkt, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index c157b0e304..b03a9ce4d1 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. 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 @@ -38,7 +38,9 @@ result_codes/1, send_ok/1, send_nok/1, + send_eval/1, send_bad_answer/1, + send_protocol_error/1, send_arbitrary/1, send_unknown/1, send_unknown_mandatory/1, @@ -47,6 +49,9 @@ send_unsupported_app/1, send_error_bit/1, send_unsupported_version/1, + send_invalid_avp_bits/1, + send_invalid_avp_length/1, + send_invalid_reject/1, send_long/1, send_nopeer/1, send_noapp/1, @@ -112,17 +117,23 @@ %% Sequence mask for End-to-End and Hop-by-Hop identifiers. -define(CLIENT_MASK, {1,26}). %% 1 in top 6 bits -%% Run tests cases in different encoding variants. Send outgoing -%% messages as lists or records. +%% How to construct messages, as record or list. -define(ENCODINGS, [list, record]). -%% Identifers for client connections. --define(CONNECTIONS, [c1,c2,c3]). +%% How to send answers, in a diameter_packet or not. +-define(CONTAINERS, [pkt, msg]). + +%% Send over multiple connections that are mapped onto +%% [{E,P} || E <- ?ENCODINGS, P <- ?CONTAINERS]. +-define(CONNECTIONS, [c0,c1,c2,c3]). %% Not really what we should be setting unless the message is sent in %% the common application but diameter doesn't care. -define(APP_ID, ?DIAMETER_APP_ID_COMMON). +%% An Application-ID the server doesn't support. +-define(BAD_APP, 42). + %% Config for diameter:start_service/2. -define(SERVICE(Name), [{'Origin-Host', Name ++ "." ++ ?REALM}, @@ -158,6 +169,8 @@ ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_REALM_NOT_SERVED'). -define(UNABLE_TO_DELIVER, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_UNABLE_TO_DELIVER'). +-define(INVALID_AVP_LENGTH, + ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_AVP_LENGTH'). -define(EVENT_RECORD, ?'DIAMETER_BASE_ACCOUNTING-RECORD-TYPE_EVENT_RECORD'). @@ -170,6 +183,8 @@ ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). -define(BAD_ANSWER, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_BAD_ANSWER'). +-define(USER_MOVED, + ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_USER_MOVED'). -define(A, list_to_atom). -define(L, atom_to_list). @@ -183,14 +198,17 @@ suite() -> all() -> [start, start_services, add_transports, result_codes] - ++ [{group, ?util:name([E,C]), P} || E <- ?ENCODINGS, - C <- ?CONNECTIONS, - P <- [[], [parallel]]] + ++ [{group, ?util:name([R,C,A]), P} || R <- ?ENCODINGS, + C <- ?CONTAINERS, + A <- ?ENCODINGS, + P <- [[], [parallel]]] ++ [remove_transports, stop_services, stop]. groups() -> Ts = tc(), - [{?util:name([E,C]), [], Ts} || E <- ?ENCODINGS, C <- ?CONNECTIONS]. + [{?util:name([R,C,A]), [], Ts} || R <- ?ENCODINGS, + C <- ?CONTAINERS, + A <- ?ENCODINGS]. init_per_group(Name, Config) -> [{group, Name} | Config]. @@ -209,7 +227,9 @@ end_per_testcase(_, _) -> tc() -> [send_ok, send_nok, + send_eval, send_bad_answer, + send_protocol_error, send_arbitrary, send_unknown, send_unknown_mandatory, @@ -218,6 +238,9 @@ tc() -> send_unsupported_app, send_error_bit, send_unsupported_version, + send_invalid_avp_bits, + send_invalid_avp_length, + send_invalid_reject, send_long, send_nopeer, send_noapp, @@ -260,7 +283,9 @@ start_services(_Config) -> add_transports(Config) -> LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx/2}]), - Cs = [?util:connect(?CLIENT, tcp, LRef, [{id, C}]) || C <- ?CONNECTIONS], + Cs = [?util:connect(?CLIENT, tcp, LRef, [{id, C}, + {capabilities, [osi(C)]}]) + || C <- ?CONNECTIONS], ?util:write_priv(Config, "transport", [LRef | Cs]). remove_transports(Config) -> @@ -278,11 +303,15 @@ capx(_, #diameter_caps{origin_host = {OH,DH}}) -> io:format("connection: ~p -> ~p~n", [DH,OH]), ok. +osi(Id) -> + [$c,N] = atom_to_list(Id), + {'Origin-State-Id', N - $0}. + %% =========================================================================== %% Ensure that result codes have the expected values. result_codes(_Config) -> - {2001, 3001, 3002, 3003, 3004, 3007, 3008, 3009, 5001, 5011} + {2001, 3001, 3002, 3003, 3004, 3007, 3008, 3009, 5001, 5011, 5014} = {?SUCCESS, ?COMMAND_UNSUPPORTED, ?UNABLE_TO_DELIVER, @@ -292,13 +321,14 @@ result_codes(_Config) -> ?INVALID_HDR_BITS, ?INVALID_AVP_BITS, ?AVP_UNSUPPORTED, - ?UNSUPPORTED_VERSION}. + ?UNSUPPORTED_VERSION, + ?INVALID_AVP_LENGTH}. %% Send an ACR and expect success. send_ok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 1}], - + #diameter_base_accounting_ACA{'Result-Code' = ?SUCCESS} = call(Config, Req). @@ -306,10 +336,18 @@ send_ok(Config) -> send_nok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 0}], - + #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} = call(Config, Req). +%% Send an ACR and expect success. +send_eval(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 3}], + + #diameter_base_accounting_ACA{'Result-Code' = ?SUCCESS} + = call(Config, Req). + %% Send an accounting ACR that the server tries to answer with an %% inappropriate header, resulting in no answer being sent and the %% request timing out. @@ -318,6 +356,15 @@ send_bad_answer(Config) -> {'Accounting-Record-Number', 2}], {error, timeout} = call(Config, Req). +%% Send an ACR that the server callback answers explicitly with a +%% protocol error. +send_protocol_error(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 4}], + + #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY} + = call(Config, Req). + %% Send an ASR with an arbitrary AVP and expect success and the same %% AVP in the reply. send_arbitrary(Config) -> @@ -385,6 +432,29 @@ send_unsupported_version(Config) -> #diameter_base_STA{'Result-Code' = ?UNSUPPORTED_VERSION} = call(Config, Req). +%% Send a request containing an incorrect AVP length. +send_invalid_avp_bits(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + + #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} + = call(Config, Req). + +%% Send a request containing an AVP length that doesn't match the +%% AVP's type. +send_invalid_avp_length(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + + #'diameter_base_STA'{'Result-Code' = ?INVALID_AVP_LENGTH} + = call(Config, Req). + +%% Send a request containing 5xxx errors that the server rejects with +%% 3xxx. +send_invalid_reject(Config) -> + Req = ['STR', {'Termination-Cause', ?USER_MOVED}], + + #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY} + = call(Config, Req). + %% Send something long that will be fragmented by TCP. send_long(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, @@ -559,21 +629,44 @@ call(Config, Req) -> call(Config, Req, Opts) -> Name = proplists:get_value(testcase, Config), - [Encoding, Client] = ?util:name(proplists:get_value(group, Config)), + [Encoding, C, E] = ?util:name(proplists:get_value(group, Config)), diameter:call(?CLIENT, dict(Req), - req(Req, Encoding), - [{extra, [Name, Client]} | Opts]). + msg(Req, Encoding), + [{extra, [Name, client(E,C)]} | Opts]). + +client(E, C) -> + list_to_atom([$c, $0 + 2*codec(E) + container(C)]). + +client(N) -> + {codec(N bsr 1), container(N rem 2)}. -req(['ACR' = H | T], record) -> +codec(record) -> 0; +codec(list) -> 1; +codec(0) -> record; +codec(1) -> list. + +%% Here we're just mapping booleans but the readable atoms are part of +%% (constructed) group names, so it's good that they're readable. + +container(pkt) -> 0; +container(msg) -> 1; +container(0) -> pkt; +container(1) -> msg. + +msg([H|T], record) + when H == 'ACR'; + H == 'ACA' -> ?ACCT:'#new-'(?ACCT:msg2rec(H), T); -req([H|T], record) -> +msg([H|T], record) -> ?BASE:'#new-'(?BASE:msg2rec(H), T); -req(T, _) -> +msg(T, _) -> T. dict(['ACR' | _]) -> ?ACCT; +dict(#diameter_base_accounting_ACR{}) -> + ?ACCT; dict(_) -> ?BASE. @@ -647,6 +740,40 @@ prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, _, _) -> log(#diameter_packet{} = P, T) -> io:format("~p: ~p~n", [T,P]). +%% prepare/3 + +prepare(Pkt, Caps, send_invalid_avp_bits) -> + Req = prepare(Pkt, Caps), + %% Last AVP in our STR is Termination-Cause of type Unsigned32: + %% set its length improperly. + #diameter_packet{header = #diameter_header{length = L}, + bin = B} + = E + = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + Offset = L - 7, %% to AVP Length + <<H:Offset/binary, 12:24/integer, T:4/binary>> = B, + E#diameter_packet{bin = <<H/binary, 13:24/integer, T/binary>>}; + +prepare(Pkt, Caps, N) + when N == send_invalid_avp_length; + N == send_invalid_reject -> + Req = prepare(Pkt, Caps), + %% Second last AVP in our STR is Auth-Application-Id of type + %% Unsigned32: Send a value of length 8. + #diameter_packet{header = #diameter_header{length = L}, + bin = B0} + = E + = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + Offset = L - 7 - 12, %% to AVP Length + <<H0:Offset/binary, 12:24/integer, T:16/binary>> = B0, + <<V, L:24/integer, H/binary>> = H0, %% assert + E#diameter_packet{bin = <<V, + (L+4):24/integer, + H/binary, + 16:24/integer, + 0:32/integer, + T/binary>>}; + prepare(Pkt, Caps, send_unsupported) -> Req = prepare(Pkt, Caps), #diameter_packet{bin = <<H:5/binary, _CmdCode:3/binary, T/binary>>} @@ -659,7 +786,7 @@ prepare(Pkt, Caps, send_unsupported_app) -> #diameter_packet{bin = <<H:8/binary, _ApplId:4/binary, T/binary>>} = E = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), - E#diameter_packet{bin = <<H/binary, 42:32/integer, T/binary>>}; + E#diameter_packet{bin = <<H/binary, ?BAD_APP:32/integer, T/binary>>}; prepare(Pkt, Caps, send_error_bit) -> #diameter_packet{header = Hdr} = Pkt, @@ -678,6 +805,8 @@ prepare(Pkt, Caps, send_anything) -> prepare(Pkt, Caps, _Name) -> prepare(Pkt, Caps). +%% prepare/2 + prepare(#diameter_packet{msg = Req}, Caps) when is_record(Req, diameter_base_accounting_ACR); 'ACR' == hd(Req) -> @@ -741,9 +870,26 @@ handle_answer(Pkt, Req, ?CLIENT, Peer, Name, _Id) -> handle_answer(Pkt, _Req, ?CLIENT, _Peer, send_detach, _Id, {Pid, Ref}) -> Pid ! {Ref, Pkt}. -answer(#diameter_packet{msg = Rec, errors = []}, _Req, _Peer, _) -> +answer(Pkt, Req, _Peer, Name) -> + #diameter_packet{header = H, msg = Rec, errors = Es} = Pkt, + ApplId = app(Req, Name), + #diameter_header{application_id = ApplId} = H, %% assert + answer(Rec, Es, Name). + +answer(Rec, [_|_], N) + when N == send_invalid_avp_bits; + N == send_invalid_avp_length; + N == send_invalid_reject -> + Rec; +answer(Rec, [], _) -> Rec. +app(_, send_unsupported_app) -> + ?BAD_APP; +app(Req, _) -> + Dict = dict(Req), + Dict:id(). + %% handle_error/6 handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Id) -> @@ -761,12 +907,24 @@ handle_request(#diameter_packet{header = H, msg = M}, ?SERVER, {_Ref, Caps}) -> {V,B} = ?CLIENT_MASK, V = EI bsr B, %% assert V = HI bsr B, %% - request(M, Caps). + #diameter_caps{origin_state_id = {_,[N]}} = Caps, + answer(client(N), request(M, Caps)). + +answer(T, {Tag, Action, Post}) -> + {Tag, answer(T, Action), Post}; +answer({E,C}, {reply, Ans}) -> + answer(C, {reply, msg(Ans, E)}); +answer(pkt, {reply, Ans}) -> + {reply, #diameter_packet{msg = Ans}}; +answer(_, T) -> + T. +%% send_nok request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0}, _) -> {eval_packet, {protocol_error, ?INVALID_AVP_BITS}, [fun log/2, invalid]}; +%% send_bad_answer request(#diameter_base_accounting_ACR{'Session-Id' = SId, 'Accounting-Record-Type' = RT, 'Accounting-Record-Number' = 2 = RN}, @@ -779,12 +937,27 @@ request(#diameter_base_accounting_ACR{'Session-Id' = SId, {'Accounting-Record-Type', RT}, {'Accounting-Record-Number', RN}], - {reply, #diameter_packet{header = #diameter_header{is_error = true},%% not + {reply, #diameter_packet{header = #diameter_header{is_error = true},%% NOT msg = Ans}}; +%% send_eval +request(#diameter_base_accounting_ACR{'Session-Id' = SId, + 'Accounting-Record-Type' = RT, + 'Accounting-Record-Number' = 3 = RN}, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}}) -> + Ans = ['ACA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Accounting-Record-Type', RT}, + {'Accounting-Record-Number', RN}], + {eval, {reply, Ans}, {erlang, now, []}}; + +%% send_ok request(#diameter_base_accounting_ACR{'Session-Id' = SId, 'Accounting-Record-Type' = RT, - 'Accounting-Record-Number' = RN}, + 'Accounting-Record-Number' = 1 = RN}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> {reply, ['ACA', {'Result-Code', ?SUCCESS}, @@ -794,26 +967,42 @@ request(#diameter_base_accounting_ACR{'Session-Id' = SId, {'Accounting-Record-Type', RT}, {'Accounting-Record-Number', RN}]}; +%% send_protocol_error +request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 4}, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}}) -> + Ans = ['answer-message', {'Result-Code', ?TOO_BUSY}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}], + {reply, Ans}; + request(#diameter_base_ASR{'Session-Id' = SId, 'AVP' = Avps}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> - {reply, #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'Session-Id' = SId, - 'Origin-Host' = OH, - 'Origin-Realm' = OR, - 'AVP' = Avps}}; + {reply, ['ASA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'AVP', Avps}]}; + +%% send_invalid_reject +request(#diameter_base_STR{'Termination-Cause' = ?USER_MOVED}, _Caps) -> + {protocol_error, ?TOO_BUSY}; +%% send_noreply request(#diameter_base_STR{'Termination-Cause' = T}, _Caps) when T /= ?LOGOUT -> discard; +%% send_destination_5 request(#diameter_base_STR{'Destination-Realm'= R}, #diameter_caps{origin_realm = {OR, _}}) when R /= undefined, R /= OR -> {protocol_error, ?REALM_NOT_SERVED}; +%% send_destination_6 request(#diameter_base_STR{'Destination-Host'= [H]}, #diameter_caps{origin_host = {OH, _}}) when H /= OH -> @@ -822,11 +1011,12 @@ request(#diameter_base_STR{'Destination-Host'= [H]}, request(#diameter_base_STR{'Session-Id' = SId}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> - {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, - 'Session-Id' = SId, - 'Origin-Host' = OH, - 'Origin-Realm' = OR}}; + {reply, ['STA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}]}; +%% send_error request(#diameter_base_RAR{}, _Caps) -> receive after 2000 -> ok end, {protocol_error, ?TOO_BUSY}. diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 5898e125ae..80b1769d04 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -40,7 +40,8 @@ MODULES = \ diameter_relay_SUITE \ diameter_tls_SUITE \ diameter_failover_SUITE \ - diameter_dpr_SUITE + diameter_dpr_SUITE \ + diameter_event_SUITE HRL_FILES = \ diameter_ct.hrl |