1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2010-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Tests of traffic between six Diameter nodes connected as follows. 23%% 24%% ---- SERVER.REALM1 (TLS after capabilities exchange) 25%% / 26%% / ---- SERVER.REALM2 (ditto) 27%% | / 28%% CLIENT.REALM0 ----- SERVER.REALM3 (no security) 29%% | \ 30%% \ ---- SERVER.REALM4 (TLS at connection establishment) 31%% \ 32%% ---- SERVER.REALM5 (ditto) 33%% 34 35-module(diameter_tls_SUITE). 36 37-export([suite/0, 38 all/0, 39 groups/0, 40 init_per_suite/1, 41 end_per_suite/1]). 42 43%% testcases 44-export([start_ssl/1, 45 start_diameter/1, 46 make_certs/1, make_certs/0, 47 start_services/1, 48 add_transports/1, 49 send1/1, 50 send2/1, 51 send3/1, 52 send4/1, 53 send5/1, 54 remove_transports/1, 55 stop_services/1, 56 stop_diameter/1, 57 stop_ssl/1]). 58 59%% diameter callbacks 60-export([prepare_request/3, 61 prepare_retransmit/3, 62 handle_answer/4, 63 handle_request/3]). 64 65-include("diameter.hrl"). 66-include("diameter_gen_base_rfc3588.hrl"). 67 68%% =========================================================================== 69 70-define(util, diameter_util). 71 72-define(ADDR, {127,0,0,1}). 73 74-define(CLIENT, "CLIENT.REALM0"). 75-define(SERVER1, "SERVER.REALM1"). 76-define(SERVER2, "SERVER.REALM2"). 77-define(SERVER3, "SERVER.REALM3"). 78-define(SERVER4, "SERVER.REALM4"). 79-define(SERVER5, "SERVER.REALM5"). 80 81-define(SERVERS, [?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). 82 83-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). 84 85-define(APP_ALIAS, the_app). 86-define(APP_ID, ?DICT_COMMON:id()). 87 88-define(NO_INBAND_SECURITY, 0). 89-define(TLS, 1). 90 91%% Config for diameter:start_service/2. 92-define(SERVICE(Host, Dict), 93 [{'Origin-Host', Host}, 94 {'Origin-Realm', realm(Host)}, 95 {'Host-IP-Address', [?ADDR]}, 96 {'Vendor-Id', 12345}, 97 {'Product-Name', "OTP/diameter"}, 98 {'Inband-Security-Id', [?NO_INBAND_SECURITY]}, 99 {'Auth-Application-Id', [Dict:id()]}, 100 {application, [{alias, ?APP_ALIAS}, 101 {dictionary, Dict}, 102 {module, #diameter_callback{peer_up = false, 103 peer_down = false, 104 pick_peer = false, 105 handle_error = false, 106 default = ?MODULE}}, 107 {answer_errors, callback}]}]). 108 109%% Config for diameter:add_transport/2. In the listening case, listen 110%% on a free port that we then lookup using the implementation detail 111%% that diameter_tcp registers the port with diameter_reg. 112-define(CONNECT(PortNr, Caps, Opts), 113 {connect, [{transport_module, diameter_tcp}, 114 {transport_config, [{raddr, ?ADDR}, 115 {rport, PortNr}, 116 {ip, ?ADDR}, 117 {port, 0} 118 | Opts]}, 119 {capabilities, Caps}]}). 120-define(LISTEN(Caps, Opts), 121 {listen, [{transport_module, diameter_tcp}, 122 {transport_config, [{ip, ?ADDR}, {port, 0} | Opts]}, 123 {capabilities, Caps}]}). 124 125-define(SUCCESS, 2001). 126-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). 127 128%% =========================================================================== 129 130suite() -> 131 [{timetrap, {seconds, 60}}]. 132 133all() -> 134 [start_ssl, 135 start_diameter, 136 make_certs, 137 start_services, 138 add_transports, 139 {group, all}, 140 {group, all, [parallel]}, 141 remove_transports, 142 stop_services, 143 stop_diameter, 144 stop_ssl]. 145 146groups() -> 147 [{all, [], tc()}]. 148 149%% Shouldn't really have to know about crypto here but 'ok' from 150%% ssl:start() isn't enough to guarantee that TLS is available. 151init_per_suite(Config) -> 152 try 153 false /= os:find_executable("openssl") 154 orelse throw({?MODULE, no_openssl}), 155 ok == (catch crypto:start()) 156 orelse throw({?MODULE, no_crypto}), 157 Config 158 catch 159 {?MODULE, E} -> 160 {skip, E} 161 end. 162 163end_per_suite(_Config) -> 164 crypto:stop(). 165 166%% Testcases to run when services are started and connections 167%% established. 168tc() -> 169 [send1, 170 send2, 171 send3, 172 send4, 173 send5]. 174 175%% =========================================================================== 176%% testcases 177 178start_ssl(_Config) -> 179 ok = ssl:start(). 180 181start_diameter(_Config) -> 182 ok = diameter:start(). 183 184make_certs() -> 185 [{timetrap, {minutes, 2}}]. 186 187make_certs(Config) -> 188 Dir = proplists:get_value(priv_dir, Config), 189 190 [] = ?util:run([[fun make_cert/2, Dir, B] || B <- ["server1", 191 "server2", 192 "server4", 193 "server5", 194 "client"]]). 195 196start_services(Config) -> 197 Dir = proplists:get_value(priv_dir, Config), 198 Servers = [server(S, sopts(S, Dir)) || S <- ?SERVERS], 199 200 ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, ?DICT_COMMON)), 201 202 {save_config, [Dir | Servers]}. 203 204add_transports(Config) -> 205 {_, [Dir | Servers]} = proplists:get_value(saved_config, Config), 206 207 true = diameter:subscribe(?CLIENT), 208 209 Opts = ssl_options(Dir, "client"), 210 Connections = [connect(?CLIENT, S, copts(N, Opts)) 211 || {S,N} <- lists:zip(Servers, ?SERVERS)], 212 213 ?util:write_priv(Config, "cfg", lists:zip(Servers, Connections)). 214 215 216%% Remove the client transports and expect the corresponding server 217%% transport to go down. 218remove_transports(Config) -> 219 Ts = ?util:read_priv(Config, "cfg"), 220 [] = [T || S <- ?SERVERS, T <- [diameter:subscribe(S)], T /= true], 221 lists:map(fun disconnect/1, Ts). 222 223stop_services(_Config) -> 224 [] = [{H,T} || H <- [?CLIENT | ?SERVERS], 225 T <- [diameter:stop_service(H)], 226 T /= ok]. 227 228stop_diameter(_Config) -> 229 ok = diameter:stop(). 230 231stop_ssl(_Config) -> 232 ok = ssl:stop(). 233 234%% Send an STR intended for a specific server and expect success. 235send1(_Config) -> 236 call(?SERVER1). 237send2(_Config) -> 238 call(?SERVER2). 239send3(_Config) -> 240 call(?SERVER3). 241send4(_Config) -> 242 call(?SERVER4). 243send5(_Config) -> 244 call(?SERVER5). 245 246%% =========================================================================== 247%% diameter callbacks 248 249%% prepare_request/3 250 251prepare_request(#diameter_packet{msg = Req}, 252 ?CLIENT, 253 {_Ref, Caps}) -> 254 #diameter_caps{origin_host = {OH, _}, 255 origin_realm = {OR, _}} 256 = Caps, 257 258 {send, set(Req, [{'Session-Id', diameter:session_id(OH)}, 259 {'Origin-Host', OH}, 260 {'Origin-Realm', OR}])}. 261 262%% prepare_retransmit/3 263 264prepare_retransmit(_Pkt, false, _Peer) -> 265 discard. 266 267%% handle_answer/4 268 269handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> 270 #diameter_packet{msg = Rec, errors = []} = Pkt, 271 Rec. 272 273%% handle_request/3 274 275handle_request(#diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId}}, 276 OH, 277 {_Ref, #diameter_caps{origin_host = {OH,_}, 278 origin_realm = {OR, _}}}) 279 when OH /= ?CLIENT -> 280 {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, 281 'Session-Id' = SId, 282 'Origin-Host' = OH, 283 'Origin-Realm' = OR}}. 284 285%% =========================================================================== 286%% support functions 287 288call(Server) -> 289 Realm = realm(Server), 290 Req = ['STR', {'Destination-Realm', Realm}, 291 {'Termination-Cause', ?LOGOUT}, 292 {'Auth-Application-Id', ?APP_ID}], 293 #diameter_base_STA{'Result-Code' = ?SUCCESS, 294 'Origin-Host' = Server, 295 'Origin-Realm' = Realm} 296 = call(Req, [{filter, realm}]). 297 298call(Req, Opts) -> 299 diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). 300 301set([H|T], Vs) -> 302 [H | Vs ++ T]. 303 304disconnect({{LRef, _PortNr}, CRef}) -> 305 ok = diameter:remove_transport(?CLIENT, CRef), 306 receive #diameter_event{info = {down, LRef, _, _}} -> ok end. 307 308realm(Host) -> 309 tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). 310 311inband_security(Ids) -> 312 [{'Inband-Security-Id', Ids}]. 313 314ssl_options(Dir, Base) -> 315 Root = filename:join([Dir, Base]), 316 [{ssl_options, [{certfile, Root ++ "_ca.pem"}, 317 {keyfile, Root ++ "_key.pem"}]}]. 318 319make_cert(Dir, Base) -> 320 make_cert(Dir, Base ++ "_key.pem", Base ++ "_ca.pem"). 321 322make_cert(Dir, Keyfile, Certfile) -> 323 [KP,CP] = [filename:join([Dir, F]) || F <- [Keyfile, Certfile]], 324 325 KC = join(["openssl genrsa -out", KP, "2048"]), 326 CC = join(["openssl req -new -x509 -key", KP, "-out", CP, "-days 7", 327 "-subj /C=SE/ST=./L=Stockholm/CN=www.erlang.org"]), 328 329 %% Hope for the best and only check that files are written. 330 KR = os:cmd(KC), 331 {_, {ok, _}} = {KR, file:read_file_info(KP)}, 332 CR = os:cmd(CC), 333 {_, {ok, _}} = {CR, file:read_file_info(CP)}, 334 335 {KP,CP}. 336 337join(Strs) -> 338 string:join(Strs, " "). 339 340%% server/2 341 342server(Host, {Caps, Opts}) -> 343 ok = diameter:start_service(Host, ?SERVICE(Host, ?DICT_COMMON)), 344 {ok, LRef} = diameter:add_transport(Host, ?LISTEN(Caps, Opts)), 345 {LRef, hd([_] = ?util:lport(tcp, LRef))}. 346 347sopts(?SERVER1, Dir) -> 348 {inband_security([?TLS]), 349 ssl_options(Dir, "server1")}; 350sopts(?SERVER2, Dir) -> 351 {inband_security([?NO_INBAND_SECURITY, ?TLS]), 352 ssl_options(Dir, "server2")}; 353sopts(?SERVER3, _) -> 354 {[], []}; 355sopts(?SERVER4, Dir) -> 356 {[], ssl(ssl_options(Dir, "server4"))}; 357sopts(?SERVER5, Dir) -> 358 {[], ssl(ssl_options(Dir, "server5"))}. 359 360ssl([{ssl_options = T, Opts}]) -> 361 [{T, true} | Opts]. 362 363%% connect/3 364 365connect(Host, {_LRef, PortNr}, {Caps, Opts}) -> 366 {ok, Ref} = diameter:add_transport(Host, ?CONNECT(PortNr, Caps, Opts)), 367 receive 368 #diameter_event{service = Host, 369 info = {up, Ref, _, _, #diameter_packet{}}} -> 370 ok 371 end, 372 Ref. 373 374copts(S, Opts) 375 when S == ?SERVER1; 376 S == ?SERVER2; 377 S == ?SERVER3 -> 378 {inband_security([?NO_INBAND_SECURITY, ?TLS]), Opts}; 379copts(S, Opts) 380 when S == ?SERVER4; 381 S == ?SERVER5 -> 382 {[], ssl(Opts)}. 383