1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2017-2020. 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-module(ssl_dist_test_lib).
22
23-behaviour(ct_suite).
24
25-include_lib("common_test/include/ct.hrl").
26-include_lib("public_key/include/public_key.hrl").
27-include("ssl_dist_test_lib.hrl").
28
29-export([tstsrvr_format/2, send_to_tstcntrl/1]).
30-export([apply_on_ssl_node/4, apply_on_ssl_node/2]).
31-export([stop_ssl_node/1, start_ssl_node/2]).
32%%
33-export([cnct2tstsrvr/1]).
34
35-define(AWAIT_SSL_NODE_UP_TIMEOUT, 30000).
36
37
38
39%% ssl_node side api
40%%
41
42tstsrvr_format(Fmt, ArgList) ->
43    send_to_tstsrvr({format, Fmt, ArgList}).
44
45send_to_tstcntrl(Message) ->
46    send_to_tstsrvr({message, Message}).
47
48
49%%
50%% test_server side api
51%%
52
53apply_on_ssl_node(
54  #node_handle{connection_handler = Hndlr} = Node,
55  M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
56    Ref = erlang:monitor(process, Hndlr),
57    apply_on_ssl_node(Node, Ref, {apply, self(), Ref, M, F, A}).
58
59apply_on_ssl_node(
60  #node_handle{connection_handler = Hndlr} = Node,
61  Fun) when is_function(Fun, 0) ->
62    Ref = erlang:monitor(process, Hndlr),
63    apply_on_ssl_node(Node, Ref, {apply, self(), Ref, Fun}).
64
65apply_on_ssl_node(Node, Ref, Msg) ->
66    send_to_ssl_node(Node, Msg),
67    receive
68        {'DOWN', Ref, process, Hndlr, Reason} ->
69            exit({handler_died, Hndlr, Reason});
70	{Ref, Result} ->
71	    Result
72    end.
73
74stop_ssl_node(#node_handle{connection_handler = Handler,
75			   socket = Socket,
76			   name = Name}) ->
77    test_server:format("Trying to stop ssl node ~s.~n", [Name]),
78    Mon = erlang:monitor(process, Handler),
79    unlink(Handler),
80    case gen_tcp:send(Socket, term_to_binary(stop)) of
81	ok ->
82	    receive
83		{'DOWN', Mon, process, Handler, Reason} ->
84		    case Reason of
85			normal ->
86			    ok;
87			_ ->
88			    ct:pal(
89                              "stop_ssl_node/1 ~s Down  ~p ~n",
90                              [Name,Reason])
91		    end
92	    end;
93	Error ->
94	    erlang:demonitor(Mon, [flush]),
95	    ct:pal("stop_ssl_node/1 ~s Warning ~p ~n", [Name,Error])
96    end.
97
98start_ssl_node(Name, Args) ->
99    {ok, LSock} = gen_tcp:listen(0,
100				 [binary, {packet, 4}, {active, false}]),
101    {ok, ListenPort} = inet:port(LSock),
102    CmdLine = mk_node_cmdline(ListenPort, Name, Args),
103    test_server:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]),
104    case open_port({spawn, CmdLine}, []) of
105	Port when is_port(Port) ->
106	    unlink(Port),
107	    catch erlang:port_close(Port),
108	    case await_ssl_node_up(Name, LSock) of
109		#node_handle{} = NodeHandle ->
110		    test_server:format("Ssl node ~s started.~n", [Name]),
111		    NodeName = list_to_atom(Name ++ "@" ++ host_name()),
112		    NodeHandle#node_handle{nodename = NodeName};
113		Error ->
114		    exit({failed_to_start_node, Name, Error})
115	    end;
116	Error ->
117	    exit({failed_to_start_node, Name, Error})
118    end.
119
120host_name() ->
121    [_, Host] = string:split(atom_to_list(node()), "@"),
122    %% [$@ | Host] = lists:dropwhile(fun ($@) -> false; (_) -> true end,
123    %%     			  atom_to_list(node())),
124    Host.
125
126mk_node_cmdline(ListenPort, Name, Args) ->
127    Static = "-detached -noinput",
128    Pa = filename:dirname(code:which(?MODULE)),
129    Prog = case catch init:get_argument(progname) of
130	       {ok,[[P]]} -> P;
131	       _ -> exit(no_progname_argument_found)
132	   end,
133    NameSw = case net_kernel:longnames() of
134		 false -> "-sname ";
135		 _ -> "-name "
136	     end,
137    {ok, Pwd} = file:get_cwd(),
138    "\"" ++ Prog ++ "\" "
139	++ Static ++ " "
140	++ NameSw ++ " " ++ Name ++ " "
141	++ "-pa " ++ Pa ++ " "
142	++ "-run application start crypto -run application start public_key "
143	++ "-eval 'net_kernel:verbose(1)' "
144	++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
145	++ host_name() ++ " "
146	++ integer_to_list(ListenPort) ++ " "
147	++ Args ++ " "
148	++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " "
149        ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
150        ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
151	++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" "
152	++ "-setcookie " ++ atom_to_list(erlang:get_cookie()).
153
154%%
155%% Connection handler test_server side
156%%
157
158await_ssl_node_up(Name, LSock) ->
159    case gen_tcp:accept(LSock, ?AWAIT_SSL_NODE_UP_TIMEOUT) of
160	{ok, Socket} ->
161	    gen_tcp:close(LSock),
162	    case gen_tcp:recv(Socket, 0) of
163		{ok, Bin} ->
164		    check_ssl_node_up(Socket, Name, Bin);
165		{error, closed} ->
166		    gen_tcp:close(Socket),
167		    exit({lost_connection_with_ssl_node_before_up, Name})
168	    end;
169	{error, Error} ->
170	    gen_tcp:close(LSock),
171            test_server:format("Accept failed for ssl node ~s: ~p~n", [Name,Error]),
172	    exit({accept_failed, Error})
173    end.
174
175check_ssl_node_up(Socket, Name, Bin) ->
176    case catch binary_to_term(Bin) of
177	{'EXIT', _} ->
178	    gen_tcp:close(Socket),
179	    exit({bad_data_received_from_ssl_node, Name, Bin});
180	{ssl_node_up, NodeName} ->
181	    case list_to_atom(Name++"@"++host_name()) of
182		NodeName ->
183		    Parent = self(),
184		    Go = make_ref(),
185		    %% Spawn connection handler on test server side
186		    Pid = spawn(
187			    fun () ->
188                                    link(group_leader()),
189				    receive Go -> ok end,
190                                    process_flag(trap_exit, true),
191				    tstsrvr_con_loop(Name, Socket, Parent)
192			    end),
193		    ok = gen_tcp:controlling_process(Socket, Pid),
194		    Pid ! Go,
195		    #node_handle{connection_handler = Pid,
196				 socket = Socket,
197				 name = Name};
198		_ ->
199		    exit({unexpected_ssl_node_connected, NodeName})
200	    end;
201	Msg ->
202	    exit({unexpected_msg_instead_of_ssl_node_up, Name, Msg})
203    end.
204
205send_to_ssl_node(#node_handle{connection_handler = Hndlr}, Term) ->
206    Hndlr ! {relay_to_ssl_node, term_to_binary(Term)},
207    ok.
208
209tstsrvr_con_loop(Name, Socket, Parent) ->
210    ok = inet:setopts(Socket,[{active,once}]),
211    receive
212	{relay_to_ssl_node, Data} when is_binary(Data) ->
213	    case gen_tcp:send(Socket, Data) of
214		ok ->
215		    ok;
216		_Error ->
217		    gen_tcp:close(Socket),
218		    exit({failed_to_relay_data_to_ssl_node, Name, Data})
219	    end;
220	{tcp, Socket, Bin} ->
221	    try binary_to_term(Bin) of
222		{format, FmtStr, ArgList} ->
223		    test_server:format(FmtStr, ArgList);
224		{message, Msg} ->
225		    test_server:format("Got message ~p", [Msg]),
226		    Parent ! Msg;
227		{apply_res, To, Ref, Res} ->
228		    To ! {Ref, Res};
229		bye ->
230                    {error, closed} = gen_tcp:recv(Socket, 0),
231		    test_server:format("Ssl node ~s stopped.~n", [Name]),
232		    gen_tcp:close(Socket),
233		    exit(normal);
234		Unknown ->
235		    exit({unexpected_message_from_ssl_node, Name, Unknown})
236            catch
237                error : _ ->
238		    gen_tcp:close(Socket),
239		    exit({bad_data_received_from_ssl_node, Name, Bin})
240	    end;
241	{tcp_closed, Socket} ->
242	    gen_tcp:close(Socket),
243	    exit({lost_connection_with_ssl_node, Name});
244        {'EXIT', Parent, Reason} ->
245            exit({'EXIT', parent, Reason});
246        Unknown ->
247            exit({unknown, Unknown})
248    end,
249    tstsrvr_con_loop(Name, Socket, Parent).
250
251%%
252%% Connection handler ssl_node side
253%%
254
255% cnct2tstsrvr() is called via command line arg -run ...
256cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) ->
257    %% Spawn connection handler on ssl node side
258    ConnHandler
259	= spawn(fun () ->
260			case catch gen_tcp:connect(Host,
261						   list_to_integer(Port),
262						   [binary,
263						    {packet, 4},
264						    {active, false}]) of
265			    {ok, Socket} ->
266				notify_ssl_node_up(Socket),
267				ets:new(test_server_info,
268					[set,
269					 public,
270					 named_table,
271					 {keypos, 1}]),
272				ets:insert(test_server_info,
273					   {test_server_handler, self()}),
274				ssl_node_con_loop(Socket);
275			    Error ->
276				halt("Failed to connect to test server " ++
277					 lists:flatten(io_lib:format("Host:~p ~n Port:~p~n Error:~p~n",
278								     [Host, Port, Error])))
279			end
280		end),
281    spawn(fun () ->
282		  Mon = erlang:monitor(process, ConnHandler),
283		  receive
284		      {'DOWN', Mon, process, ConnHandler, Reason} ->
285			  receive after 1000 -> ok end,
286			  halt("test server connection handler terminated: " ++
287				   lists:flatten(io_lib:format("~p", [Reason])))
288		  end
289	  end).
290
291notify_ssl_node_up(Socket) ->
292    case catch gen_tcp:send(Socket,
293			    term_to_binary({ssl_node_up, node()})) of
294	ok -> ok;
295	_ -> halt("Failed to notify test server that I'm up")
296    end.
297
298send_to_tstsrvr(Term) ->
299    case catch ets:lookup_element(test_server_info, test_server_handler, 2) of
300	Hndlr when is_pid(Hndlr) ->
301	    Hndlr ! {relay_to_test_server, term_to_binary(Term)}, ok;
302	_ ->
303	    receive after 200 -> ok end,
304	    send_to_tstsrvr(Term)
305    end.
306
307ssl_node_con_loop(Socket) ->
308    inet:setopts(Socket,[{active,once}]),
309    receive
310	{relay_to_test_server, Data} when is_binary(Data) ->
311	    case gen_tcp:send(Socket, Data) of
312		ok ->
313		    ok;
314		_Error ->
315		    gen_tcp:close(Socket),
316		    halt("Failed to relay data to test server")
317	    end;
318	{tcp, Socket, Bin} ->
319	    case catch binary_to_term(Bin) of
320		{'EXIT', _} ->
321		    gen_tcp:close(Socket),
322		    halt("test server sent me bad data");
323		{apply, From, Ref, M, F, A} ->
324		    spawn_link(
325		      fun () ->
326			      send_to_tstsrvr({apply_res,
327					       From,
328					       Ref,
329					       (catch apply(M, F, A))})
330			  end);
331		{apply, From, Ref, Fun} ->
332		    spawn_link(fun () ->
333				       send_to_tstsrvr({apply_res,
334							From,
335							Ref,
336							(catch Fun())})
337			       end);
338		stop ->
339		    gen_tcp:send(Socket, term_to_binary(bye)),
340		    init:stop(),
341		    receive after infinity -> ok end;
342		_Unknown ->
343		    halt("test server sent me an unexpected message")
344	    end;
345	{tcp_closed, Socket} ->
346	    halt("Lost connection to test server")
347    end,
348    ssl_node_con_loop(Socket).
349