aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/test/diameter_relay_SUITE.erl
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2011-09-27 11:41:52 +0200
committerAnders Svensson <[email protected]>2011-09-27 11:41:52 +0200
commit1c9017d8970713a24a36929063bcc76a28ae54c5 (patch)
tree5d5020f812501d53da6ef5091ec8a75eebb7918b /lib/diameter/test/diameter_relay_SUITE.erl
parent6d31ccf58efcffc53a949fbdd0a217d6c3ac7058 (diff)
parent162c0d3ee30790ec5a75e20b0e2e8bc61ed92375 (diff)
downloadotp-1c9017d8970713a24a36929063bcc76a28ae54c5.tar.gz
otp-1c9017d8970713a24a36929063bcc76a28ae54c5.tar.bz2
otp-1c9017d8970713a24a36929063bcc76a28ae54c5.zip
Merge branch 'anders/diameter/testsuites/OTP-9553' into dev
* anders/diameter/testsuites/OTP-9553: (23 commits) Explicit {init,end}_per_group/2 to work around ct bug Add relay suite More traffic cases Add traffic suite Use groups for parallel testcase execution Remove gen_sctp suite since it's not diameter-specific Minor macro cleanup Minor diameter_ct simplification Improve xref testcase Don't require GNU sed to fail testsuite targets Generate dependencies makefile Makefile/spec cleanup Remove old test framework Add transport and gen_sctp suites Add watchdog suite Add stats suite Add sync suite Add reg suite Add dict suite, remove session suite Move appup tests into app suite and use systools for both ...
Diffstat (limited to 'lib/diameter/test/diameter_relay_SUITE.erl')
-rw-r--r--lib/diameter/test/diameter_relay_SUITE.erl422
1 files changed, 422 insertions, 0 deletions
diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl
new file mode 100644
index 0000000000..0cda2df8ca
--- /dev/null
+++ b/lib/diameter/test/diameter_relay_SUITE.erl
@@ -0,0 +1,422 @@
+%%
+%% %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 traffic between seven Diameter nodes connected as follows.
+%%
+%% --- SERVER1.REALM2
+%% /
+%% ---- RELAY.REALM2 ---- SERVER2.REALM2
+%% / |
+%% CLIENT.REALM1 |
+%% \ |
+%% ---- RELAY.REALM3 ---- SERVER2.REALM3
+%% \
+%% --- SERVER2.REALM3
+%%
+
+-module(diameter_relay_SUITE).
+
+-export([suite/0,
+ all/0,
+ groups/0,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_suite/1,
+ end_per_suite/1]).
+
+%% testcases
+-export([send1/1,
+ send2/1,
+ send3/1,
+ send4/1,
+ send_loop/1,
+ send_timeout_1/1,
+ send_timeout_2/1,
+ remove_transports/1,
+ stop_services/1]).
+
+%% diameter callbacks
+-export([peer_up/3,
+ peer_down/3,
+ pick_peer/4,
+ prepare_request/3,
+ prepare_retransmit/3,
+ handle_answer/4,
+ handle_error/4,
+ handle_request/3]).
+
+-ifdef(DIAMETER_CT).
+-include("diameter_gen_base_rfc3588.hrl").
+-else.
+-include_lib("diameter/include/diameter_gen_base_rfc3588.hrl").
+-endif.
+
+-include_lib("diameter/include/diameter.hrl").
+-include("diameter_ct.hrl").
+
+%% ===========================================================================
+
+-define(ADDR, {127,0,0,1}).
+
+-define(CLIENT, "CLIENT.REALM1").
+-define(RELAY1, "RELAY.REALM2").
+-define(SERVER1, "SERVER1.REALM2").
+-define(SERVER2, "SERVER2.REALM2").
+-define(RELAY2, "RELAY.REALM3").
+-define(SERVER3, "SERVER1.REALM3").
+-define(SERVER4, "SERVER2.REALM3").
+
+-define(DICT_COMMON, ?DIAMETER_DICT_COMMON).
+-define(DICT_RELAY, ?DIAMETER_DICT_RELAY).
+
+-define(APP_ALIAS, the_app).
+-define(APP_ID, ?DICT_COMMON:id()).
+
+%% Config for diameter:start_service/2.
+-define(SERVICE(Host, Dict),
+ [{'Origin-Host', Host},
+ {'Origin-Realm', realm(Host)},
+ {'Host-IP-Address', [?ADDR]},
+ {'Vendor-Id', 12345},
+ {'Product-Name', "OTP/diameter"},
+ {'Acct-Application-Id', [Dict:id()]},
+ {application, [{alias, ?APP_ALIAS},
+ {dictionary, Dict},
+ {module, ?MODULE},
+ {answer_errors, callback}]}]).
+
+%% Config for diameter:add_transport/2. In the listening case, listen
+%% on a free port that we then lookup using the implementation detail
+%% that diameter_tcp registers the port with diameter_reg.
+-define(CONNECT(PortNr),
+ {connect, [{transport_module, diameter_tcp},
+ {transport_config, [{raddr, ?ADDR},
+ {rport, PortNr},
+ {ip, ?ADDR},
+ {port, 0}]}]}).
+-define(LISTEN,
+ {listen, [{transport_module, diameter_tcp},
+ {transport_config, [{ip, ?ADDR}, {port, 0}]}]}).
+
+-define(SUCCESS, 2001).
+-define(LOOP_DETECTED, 3005).
+-define(UNABLE_TO_DELIVER, 3002).
+
+-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT').
+-define(AUTHORIZE_ONLY, ?'DIAMETER_BASE_RE-AUTH-REQUEST-TYPE_AUTHORIZE_ONLY').
+
+-define(A, list_to_atom).
+-define(L, atom_to_list).
+
+%% ===========================================================================
+
+suite() ->
+ [{timetrap, {seconds, 10}}].
+
+all() ->
+ [{group, N} || {N, _, _} <- groups()]
+ ++ [remove_transports, stop_services].
+
+groups() ->
+ Ts = tc(),
+ [{all, [], Ts},
+ {p, [parallel], Ts}].
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, _) ->
+ ok.
+
+init_per_suite(Config) ->
+ ok = diameter:start(),
+
+ dbg:tracer(port, dbg:trace_port(file, "relay.dbg")),
+ dbg:p(all,c),
+ dbg:tpl(diameter_service, x),
+ dbg:tp(?MODULE, x),
+
+
+ [S1,S2,S3,S4] = S = [server(N, ?DICT_COMMON) || N <- [?SERVER1,
+ ?SERVER2,
+ ?SERVER3,
+ ?SERVER4]],
+ [R1,R2] = R = [server(N, ?DICT_RELAY) || N <- [?RELAY1, ?RELAY2]],
+
+ ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, ?DICT_COMMON)),
+
+ true = diameter:subscribe(?RELAY1),
+ true = diameter:subscribe(?RELAY2),
+ true = diameter:subscribe(?CLIENT),
+
+ [C1,C2] = connect(?RELAY1, [S1,S2]),
+ [C3,C4] = connect(?RELAY2, [S3,S4]),
+ [C5,C6] = connect(?CLIENT, [R1,R2]),
+
+ C7 = connect(?RELAY1, R2),
+
+ [{transports, {S, R, [C1,C2,C3,C4,C5,C6,C7]}} | Config].
+
+end_per_suite(_Config) ->
+ ok = diameter:stop().
+
+%% Testcases to run when services are started and connections
+%% established.
+tc() ->
+ [send1,
+ send2,
+ send3,
+ send4,
+ send_loop,
+ send_timeout_1,
+ send_timeout_2].
+
+server(Host, Dict) ->
+ ok = diameter:start_service(Host, ?SERVICE(Host, Dict)),
+ {ok, LRef} = diameter:add_transport(Host, ?LISTEN),
+ {LRef, portnr(LRef)}.
+
+connect(Host, {_LRef, PortNr}) ->
+ {ok, Ref} = diameter:add_transport(Host, ?CONNECT(PortNr)),
+ ok = receive
+ #diameter_event{service = Host,
+ info = {up, Ref, _, _, #diameter_packet{}}} ->
+ ok
+ after 2000 ->
+ false
+ end,
+ Ref;
+connect(Host, Ports) ->
+ [connect(Host, P) || P <- Ports].
+
+portnr(LRef) ->
+ portnr(LRef, 20).
+
+portnr(LRef, N)
+ when 0 < N ->
+ case diameter_reg:match({diameter_tcp, listener, {LRef, '_'}}) of
+ [{T, _Pid}] ->
+ {_, _, {LRef, {_Addr, LSock}}} = T,
+ {ok, PortNr} = inet:port(LSock),
+ PortNr;
+ [] ->
+ receive after 50 -> ok end,
+ portnr(LRef, N-1)
+ end.
+
+realm(Host) ->
+ tl(lists:dropwhile(fun(C) -> C /= $. end, Host)).
+
+%% ===========================================================================
+
+%% Send an STR intended for a specific server and expect success.
+send1(_Config) ->
+ call(?SERVER1).
+send2(_Config) ->
+ call(?SERVER2).
+send3(_Config) ->
+ call(?SERVER3).
+send4(_Config) ->
+ call(?SERVER4).
+
+%% Send an ASR that loops between the relays and expect the loop to
+%% be detected.
+send_loop(_Config) ->
+ Req = ['ASR', {'Destination-Realm', realm(?SERVER1)},
+ {'Destination-Host', ?SERVER1},
+ {'Auth-Application-Id', ?APP_ID}],
+ #'diameter_base_answer-message'{'Result-Code' = ?LOOP_DETECTED}
+ = call(Req, [{filter, realm}]).
+
+%% Send a RAR that is incorrectly routed and then discarded and expect
+%% different results depending on whether or not we or the relay
+%% timeout first.
+send_timeout_1(_Config) ->
+ #'diameter_base_answer-message'{'Result-Code' = ?UNABLE_TO_DELIVER}
+ = send_timeout(7000).
+send_timeout_2(_Config) ->
+ {error, timeout} = send_timeout(3000).
+
+send_timeout(Tmo) ->
+ Req = ['RAR', {'Destination-Realm', realm(?SERVER1)},
+ {'Destination-Host', ?SERVER1},
+ {'Auth-Application-Id', ?APP_ID},
+ {'Re-Auth-Request-Type', ?AUTHORIZE_ONLY}],
+ call(Req, [{filter, realm}, {timeout, Tmo}]).
+
+%% Remove the client transports and expect the corresponding server
+%% transport to go down.
+remove_transports(Config) ->
+ {[S1,S2,S3,S4], [R1,R2], [C1,C2,C3,C4,C5,C6,C7]}
+ = proplists:get_value(transports, Config),
+
+ true = diameter:subscribe(?SERVER1),
+ true = diameter:subscribe(?SERVER2),
+ true = diameter:subscribe(?SERVER3),
+ true = diameter:subscribe(?SERVER4),
+ true = diameter:subscribe(?RELAY1),
+ true = diameter:subscribe(?RELAY2),
+
+ disconnect(S1, ?RELAY1, C1),
+ disconnect(S2, ?RELAY1, C2),
+ disconnect(S3, ?RELAY2, C3),
+ disconnect(S4, ?RELAY2, C4),
+ disconnect(R1, ?CLIENT, C5),
+ disconnect(R2, ?CLIENT, C6),
+ disconnect(R2, ?RELAY1, C7).
+
+disconnect({LRef, _PortNr}, Client, CRef) ->
+ ok = diameter:remove_transport(Client, CRef),
+ ok = receive #diameter_event{info = {down, LRef, _, _}} -> ok
+ after 2000 -> false
+ end.
+
+stop_services(_Config) ->
+ S = [?CLIENT, ?RELAY1, ?RELAY2, ?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4],
+ Ok = [ok || _ <- S],
+ Ok = [diameter:stop_service(H) || H <- S].
+
+%% ===========================================================================
+
+call(Server) ->
+ Realm = realm(Server),
+ Req = ['STR', {'Destination-Realm', Realm},
+ {'Destination-Host', [Server]},
+ {'Termination-Cause', ?LOGOUT},
+ {'Auth-Application-Id', ?APP_ID}],
+ #diameter_base_STA{'Result-Code' = ?SUCCESS,
+ 'Origin-Host' = Server,
+ 'Origin-Realm' = Realm}
+ = call(Req, [{filter, realm}]).
+
+call(Req, Opts) ->
+ diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts).
+
+set([H|T], Vs) ->
+ [H | Vs ++ T].
+
+%% ===========================================================================
+%% diameter callbacks
+
+%% peer_up/3
+
+peer_up(_SvcName, _Peer, State) ->
+ State.
+
+%% peer_down/3
+
+peer_down(_SvcName, _Peer, State) ->
+ State.
+
+%% pick_peer/4
+
+pick_peer([Peer | _], _, Svc, _State)
+ when Svc == ?RELAY1;
+ Svc == ?RELAY2;
+ Svc == ?CLIENT->
+ {ok, Peer}.
+
+%% prepare_request/3
+
+prepare_request(Pkt, Svc, _Peer)
+ when Svc == ?RELAY1;
+ Svc == ?RELAY2 ->
+ {send, Pkt};
+
+prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) ->
+ {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}]).
+
+%% prepare_retransmit/3
+
+prepare_retransmit(_Pkt, false, _Peer) ->
+ discard.
+
+%% handle_answer/4
+
+%% A relay must return Pkt.
+handle_answer(Pkt, _Req, Svc, _Peer)
+ when Svc == ?RELAY1;
+ Svc == ?RELAY2 ->
+ Pkt;
+
+handle_answer(Pkt, _Req, ?CLIENT, _Peer) ->
+ #diameter_packet{msg = Rec, errors = []} = Pkt,
+ Rec.
+
+%% handle_error/4
+
+handle_error(Reason, _Req, _Svc, _Peer) ->
+ {error, Reason}.
+
+%% handle_request/3
+
+handle_request(Pkt, OH, {_Ref, #diameter_caps{origin_host = {OH,_}} = Caps})
+ when OH /= ?CLIENT ->
+ request(Pkt, Caps).
+
+%% RELAY1 routes any ASR or RAR to RELAY2 ...
+request(#diameter_packet{header = #diameter_header{cmd_code = C}},
+ #diameter_caps{origin_host = {?RELAY1, _}})
+ when C == 274; %% ASR
+ C == 258 -> %% RAR
+ {relay, [{filter, {realm, realm(?RELAY2)}}]};
+
+%% ... which in turn routes it back. Expect diameter to either answer
+%% either with DIAMETER_LOOP_DETECTED/DIAMETER_UNABLE_TO_COMPLY.
+request(#diameter_packet{header = #diameter_header{cmd_code = 274}},
+ #diameter_caps{origin_host = {?RELAY2, _}}) ->
+ {relay, [{filter, {host, ?RELAY1}}]};
+request(#diameter_packet{header = #diameter_header{cmd_code = 258}},
+ #diameter_caps{origin_host = {?RELAY2, _}}) ->
+ discard;
+
+%% Other request to a relay: send on to one of the servers in the
+%% same realm.
+request(_Pkt, #diameter_caps{origin_host = {OH, _}})
+ when OH == ?RELAY1;
+ OH == ?RELAY2 ->
+ {relay, [{filter, {all, [host, realm]}}]};
+
+%% Request received by a server: answer.
+request(#diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId,
+ 'Origin-Host' = Host,
+ 'Origin-Realm' = Realm,
+ 'Route-Record' = Route}},
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}) ->
+ %% The request should have the Origin-Host/Realm of the original
+ %% sender.
+ R = realm(?CLIENT),
+ {?CLIENT, R} = {Host, Realm},
+ %% A relay appends the identity of the peer that a request was
+ %% received from to the Route-Record avp.
+ [?CLIENT] = Route,
+ {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS,
+ 'Session-Id' = SId,
+ 'Origin-Host' = OH,
+ 'Origin-Realm' = OR}}.