1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-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-module(erl_distribution_wb_SUITE).
21
22-include_lib("common_test/include/ct.hrl").
23-include_lib("kernel/include/inet.hrl").
24
25-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
26	 init_per_group/2,end_per_group/2]).
27
28-export([init_per_testcase/2, end_per_testcase/2, whitebox/1,
29	 switch_options/1, missing_compulsory_dflags/1]).
30
31
32-define(to_port(Socket, Data),
33	case inet_tcp:send(Socket, Data) of
34	    {error, closed} ->
35		self() ! {tcp_closed, Socket},
36	        {error, closed};
37	    R ->
38	        R
39        end).
40
41-define(DIST_VER_HIGH, 6).
42-define(DIST_VER_LOW, 5).
43
44-define(DFLAG_PUBLISHED,1).
45-define(DFLAG_ATOM_CACHE,2).
46-define(DFLAG_EXTENDED_REFERENCES,4).
47-define(DFLAG_DIST_MONITOR,8).
48-define(DFLAG_FUN_TAGS,16#10).
49-define(DFLAG_DIST_MONITOR_NAME,16#20).
50-define(DFLAG_HIDDEN_ATOM_CACHE,16#40).
51-define(DFLAG_NEW_FUN_TAGS,16#80).
52-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
53-define(DFLAG_UTF8_ATOMS, 16#10000).
54-define(DFLAG_BIG_CREATION, 16#40000).
55-define(DFLAG_HANDSHAKE_23, 16#01000000).
56
57%% From R9 and forward extended references is compulsory
58%% From R10 and forward extended pids and ports are compulsory
59%% From R20 and forward UTF8 atoms are compulsory
60%% From R21 and forward NEW_FUN_TAGS is compulsory (no more tuple fallback {fun, ...})
61%% From R23 and forward BIG_CREATION is compulsory
62-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor
63                            ?DFLAG_EXTENDED_PIDS_PORTS bor
64                            ?DFLAG_UTF8_ATOMS bor
65                            ?DFLAG_NEW_FUN_TAGS bor
66                            ?DFLAG_BIG_CREATION)).
67
68-define(PASS_THROUGH, $p).
69
70-define(shutdown(X), exit(X)).
71-define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]).
72
73-define(int32(X),
74	[((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
75	 ((X) bsr 8) band 16#ff, (X) band 16#ff]).
76
77-define(i16(X1,X0),
78        (?u16(X1,X0) -
79	     (if (X1) > 127 -> 16#10000; true -> 0 end))).
80
81-define(u16(X1,X0),
82        (((X1) bsl 8) bor (X0))).
83
84-define(u32(X3,X2,X1,X0),
85        (((X3) bsl 24) bor ((X2) bsl 16) bor ((X1) bsl 8) bor (X0))).
86
87suite() ->
88    [{ct_hooks,[ts_install_cth]},
89     {timetrap,{minutes,1}}].
90
91all() ->
92    [whitebox, switch_options, missing_compulsory_dflags].
93
94groups() ->
95    [].
96
97init_per_suite(Config) ->
98    Config.
99
100end_per_suite(_Config) ->
101    ok.
102
103init_per_group(_GroupName, Config) ->
104    Config.
105
106end_per_group(_GroupName, Config) ->
107    Config.
108
109
110init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
111    Config.
112
113end_per_testcase(_Func, _Config) ->
114    ok.
115
116%% Tests switching of options for the tcp port, as this is done
117%% when the distribution port is to be shortcut into the emulator.
118%% Maybe this should be in the inet test suite, but only the distribution
119%% does such horrible things...
120switch_options(Config) when is_list(Config) ->
121    ok = test_switch_active(),
122    ok = test_switch_active_partial() ,
123    ok = test_switch_active_and_packet(),
124    ok.
125
126
127%% Whitebox testing of distribution handshakes.
128whitebox(Config) when is_list(Config) ->
129    {ok, Node} = start_node(?MODULE,""),
130    Cookie = erlang:get_cookie(),
131    {_,Host} = split(node()),
132    [begin
133         io:format("Test OurVersion=~p and TrustEpmd=~p\n",
134                   [OurVersion, TrustEpmd]),
135         ok = pending_up_md5(Node, join(ccc,Host), OurVersion,
136                             TrustEpmd, Cookie),
137         ok = simultaneous_md5(Node, join('A',Host), OurVersion,
138                               TrustEpmd, Cookie),
139         ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host),
140                               OurVersion, TrustEpmd, Cookie)
141     end
142     || OurVersion <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH),
143        TrustEpmd <- [true, false]],
144    stop_node(Node),
145    ok.
146
147%%
148%% The actual tests
149%%
150
151%%
152%% Switch tcp options test
153%%
154
155test_switch_active() ->
156    {Client, Server} = socket_pair(0, 4),
157    ok = write_packets_32(Client, 1, 5),
158    receive after 2000 -> ok end,
159    ok = read_packets(Server, 1, 1),
160    receive after 2000 -> ok end,
161    ok = read_packets(Server, 2, 2),
162    inet:setopts(Server, [{active, true}]),
163    ok = receive_packets(Server, 3, 5),
164    close_pair({Client, Server}),
165    ok.
166
167test_switch_active_partial() ->
168    {Client, Server} = socket_pair(0, 4),
169    ok = write_packets_32(Client, 1, 2),
170    ok = gen_tcp:send(Client,[?int32(4), [0,0,0]]),
171    receive after 2000 -> ok end,
172    ok = read_packets(Server, 1, 1),
173    receive after 2000 -> ok end,
174    ok = read_packets(Server, 2, 2),
175    inet:setopts(Server, [{active, true}]),
176    ok = gen_tcp:send(Client,[3]),
177    ok = write_packets_32(Client, 4, 5),
178    ok = receive_packets(Server, 3, 5),
179    close_pair({Client, Server}),
180    ok.
181
182do_test_switch_active_and_packet(SendBefore, SendAfter) ->
183    {Client, Server} = socket_pair(0, 2),
184    ok = write_packets_16(Client, 1, 2),
185    ok = gen_tcp:send(Client,SendBefore),
186    receive after 2000 -> ok end,
187    ok = read_packets(Server, 1, 1),
188    receive after 2000 -> ok end,
189    ok = read_packets(Server, 2, 2),
190    inet:setopts(Server, [{packet,4}, {active, true}]),
191    ok = gen_tcp:send(Client,SendAfter),
192    ok = write_packets_32(Client, 4, 5),
193    ok = receive_packets(Server, 3, 5),
194    close_pair({Client, Server}),
195    ok.
196
197test_switch_active_and_packet() ->
198    ok = do_test_switch_active_and_packet([0],[0,0,4,0,0,0,3]),
199    ok = do_test_switch_active_and_packet([0,0],[0,4,0,0,0,3]),
200    ok = do_test_switch_active_and_packet([0,0,0],[4,0,0,0,3]),
201    ok = do_test_switch_active_and_packet([0,0,0,4],[0,0,0,3]),
202    ok = do_test_switch_active_and_packet([0,0,0,4,0],[0,0,3]),
203    ok = do_test_switch_active_and_packet([0,0,0,4,0,0],[0,3]),
204    ok = do_test_switch_active_and_packet([0,0,0,4,0,0,0],[3]),
205    ok = do_test_switch_active_and_packet([0,0,0,4,0,0,0,3],[]),
206    ok.
207
208
209%%
210%% Handshake tests
211%%
212pending_up_md5(Node,OurName,OurVersion,TrustEpmd,Cookie) ->
213    {NA,NB} = split(Node),
214    {port,PortNo,EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
215    {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
216				    [{active,false},
217				     {packet,2}]),
218    AssumedVersion = case TrustEpmd of
219                         true -> EpmdSaysVersion;
220                         false -> ?DIST_VER_LOW
221                     end,
222    SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
223    ok = recv_status(SocketA),
224    {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
225    OurChallengeA = gen_challenge(),
226    OurDigestA = gen_digest(HisChallengeA, Cookie),
227    send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
228    send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
229    ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
230%%%
231%%% OK, one connection is up, now lets be nasty and try another up:
232%%%
233%%% But wait for a while, the other node might not have done setnode
234%%% just yet...
235    receive after 1000 -> ok end,
236    {ok, SocketB} = gen_tcp:connect(atom_to_list(NB),PortNo,
237				    [{active,false},
238				     {packet,2}]),
239    SentNameMsg = send_name(SocketB,OurName, OurVersion, AssumedVersion),
240    alive = recv_status(SocketB),
241    send_status(SocketB, true),
242    gen_tcp:close(SocketA),
243    {Node,ChallengeMsg,HisChallengeB} = recv_challenge(SocketB,OurVersion),
244    OurChallengeB = gen_challenge(),
245    OurDigestB = gen_digest(HisChallengeB, Cookie),
246    send_complement(SocketB, SentNameMsg, ChallengeMsg, OurVersion),
247    send_challenge_reply(SocketB, OurChallengeB, OurDigestB),
248    ok = recv_challenge_ack(SocketB, OurChallengeB, Cookie),
249%%%
250%%% Well, are we happy?
251%%%
252
253    inet:setopts(SocketB, [{active, false},
254			   {packet, 4}]),
255    gen_tcp:send(SocketB,build_rex_message('',OurName)),
256    {Header, Message} = recv_message(SocketB),
257    io:format("Received header ~p, data ~p.~n",
258	      [Header, Message]),
259    gen_tcp:close(SocketB),
260    ok.
261
262simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName < Node ->
263    pong = net_adm:ping(Node),
264    LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
265		  {ok, Socket} ->
266		      Socket;
267		  Else ->
268		      exit(Else)
269	      end,
270    EpmdSocket = register_node(OurName, LSocket, ?DIST_VER_LOW, ?DIST_VER_LOW),
271    {NA, NB} = split(Node),
272    rpc:cast(Node, net_adm, ping, [OurName]),
273    receive after 1000 -> ok end,
274    {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
275    {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
276				    [{active,false},
277				     {packet,2}]),
278    AssumedVersion = case TrustEpmd of
279                         true -> EpmdSaysVersion;
280                         false -> ?DIST_VER_LOW
281                     end,
282    send_name(SocketA,OurName, OurVersion, AssumedVersion),
283    %% We are still not marked up on the other side, as our first message
284    %% is not sent.
285    SocketB = case gen_tcp:accept(LSocket) of
286		  {ok, Socket1} ->
287		      Socket1;
288		  Else2 ->
289		      exit(Else2)
290	      end,
291    nok = recv_status(SocketA),
292    %% Now we are expected to close A
293    gen_tcp:close(SocketA),
294    %% But still Socket B will continue
295    {Node,GotNameMsg,GotFlags} = recv_name(SocketB),
296    true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
297    send_status(SocketB, ok_simultaneous),
298    MyChallengeB = gen_challenge(),
299    send_challenge(SocketB, OurName, MyChallengeB, OurVersion, GotFlags),
300    recv_complement(SocketB, GotNameMsg, OurVersion),
301    {ok,HisChallengeB} = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
302    DigestB = gen_digest(HisChallengeB,Cookie),
303    send_challenge_ack(SocketB, DigestB),
304    inet:setopts(SocketB, [{active, false},
305			   {packet, 4}]),
306    %% This should be the ping message.
307    {Header, Message} = recv_message(SocketB),
308    io:format("Received header ~p, data ~p.~n",
309	      [Header, Message]),
310    gen_tcp:close(SocketB),
311    gen_tcp:close(LSocket),
312    gen_tcp:close(EpmdSocket),
313    ok;
314
315simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName > Node ->
316    pong = net_adm:ping(Node),
317    LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
318		  {ok, Socket} ->
319		      Socket;
320		  Else ->
321		      exit(Else)
322	      end,
323    EpmdSocket = register_node(OurName, LSocket,
324                               ?DIST_VER_LOW, ?DIST_VER_LOW),
325    {NA, NB} = split(Node),
326    rpc:cast(Node, net_adm, ping, [OurName]),
327    receive after 1000 -> ok end,
328    {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
329    {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
330				    [{active,false},
331				     {packet,2}]),
332    SocketB = case gen_tcp:accept(LSocket) of
333		  {ok, Socket1} ->
334		      Socket1;
335		  Else2 ->
336		      exit(Else2)
337	      end,
338    AssumedVersion = case TrustEpmd of
339                         true -> EpmdSaysVersion;
340                         false -> ?DIST_VER_LOW
341                     end,
342    SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
343    ok_simultaneous = recv_status(SocketA),
344    %% Socket B should die during this
345    case catch begin
346		   {Node,GotNameMsg,GotFlagsB} = recv_name(SocketB),
347                   true = (GotFlagsB band ?DFLAG_HANDSHAKE_23) =/= 0,
348		   send_status(SocketB, ok_simultaneous),
349		   MyChallengeB = gen_challenge(),
350		   send_challenge(SocketB, OurName, MyChallengeB,
351				  OurVersion, GotFlagsB),
352                   recv_complement(SocketB, GotNameMsg, OurVersion),
353		   {ok,HisChallengeB} = recv_challenge_reply(
354				     SocketB,
355				     MyChallengeB,
356				     Cookie),
357		   DigestB = gen_digest(HisChallengeB,Cookie),
358		   send_challenge_ack(SocketB, DigestB),
359		   inet:setopts(SocketB, [{active, false},
360					  {packet, 4}]),
361		   {HeaderB, MessageB} = recv_message(SocketB),
362		   io:format("Received header ~p, data ~p.~n",
363			     [HeaderB, MessageB])
364	       end of
365	{'EXIT', Exitcode} ->
366	    io:format("Expected exitsignal caught: ~p.~n",
367		      [Exitcode]);
368	Success ->
369	    io:format("Unexpected success: ~p~n",
370		      [Success]),
371	    exit(unexpected_success)
372    end,
373    gen_tcp:close(SocketB),
374    %% But still Socket A will continue
375    {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
376    OurChallengeA = gen_challenge(),
377    OurDigestA = gen_digest(HisChallengeA, Cookie),
378    send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
379    send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
380    ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
381
382    inet:setopts(SocketA, [{active, false},
383			   {packet, 4}]),
384    gen_tcp:send(SocketA,build_rex_message('',OurName)),
385    {Header, Message} = recv_message(SocketA),
386    io:format("Received header ~p, data ~p.~n",
387	      [Header, Message]),
388    gen_tcp:close(SocketA),
389    gen_tcp:close(LSocket),
390    gen_tcp:close(EpmdSocket),
391    ok.
392
393missing_compulsory_dflags(Config) when is_list(Config) ->
394    [Name1, Name2] = get_nodenames(2, missing_compulsory_dflags),
395    {ok, Node} = start_node(Name1,""),
396    {NA,NB} = split(Node),
397    {port,PortNo,_} = erl_epmd:port_please(NA,NB),
398    [begin
399         {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
400                                         [{active,false},
401                                          {packet,2}]),
402         BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
403         send_name(SocketA,BadNode, Version, Version, 0),
404         not_allowed = recv_status(SocketA),
405         gen_tcp:close(SocketA)
406     end
407     || Version <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH)],
408    stop_node(Node),
409    ok.
410
411%%
412%% Here comes the utilities
413%%
414
415%%
416%% Switch option utilities
417%%
418write_packets_32(_, M, N) when M > N ->
419    ok;
420write_packets_32(Sock, M, N) ->
421    ok = gen_tcp:send(Sock,[?int32(4), ?int32(M)]),
422    write_packets_32(Sock, M+1, N).
423
424write_packets_16(_, M, N) when M > N ->
425    ok;
426write_packets_16(Sock, M, N) ->
427    ok = gen_tcp:send(Sock,[?int16(4), ?int32(M)]),
428    write_packets_16(Sock, M+1, N).
429
430read_packets(_, M, N) when M > N ->
431    ok;
432read_packets(Sock, M, N) ->
433    Expected = ?int32(M),
434    case gen_tcp:recv(Sock, 0) of
435	{ok, Expected} ->
436	    read_packets(Sock, M+1, N);
437	{ok, Unexpected} ->
438	    exit({unexpected_data_read, Unexpected});
439	Error ->
440	    exit({error_read, Error})
441    end.
442
443receive_packets(Sock, M, N) when M > N ->
444    receive
445	{tcp, Sock, Data} ->
446	    exit({extra_data, Data})
447    after 0 ->
448	    ok
449    end;
450
451receive_packets(Sock, M, N) ->
452    Expect = ?int32(M),
453    receive
454	{tcp, Sock, Expect} ->
455	    receive_packets(Sock, M+1, N);
456	{tcp, Sock, Unexpected} ->
457	    exit({unexpected_data_received, Unexpected})
458    after 500 ->
459	    exit({no_data_received_for,M})
460    end.
461
462socket_pair(ClientPack, ServerPack) ->
463    {ok, Listen} = gen_tcp:listen(0, [{active, false},
464				      {packet, ServerPack}]),
465    {ok, Host} = inet:gethostname(),
466    {ok, Port} = inet:port(Listen),
467    {ok, Client} = gen_tcp:connect(Host, Port, [{active, false},
468						{packet, ClientPack}]),
469    {ok, Server} = gen_tcp:accept(Listen),
470    gen_tcp:close(Listen),
471    {Client, Server}.
472
473close_pair({Client, Server}) ->
474    gen_tcp:close(Client),
475    gen_tcp:close(Server),
476    ok.
477
478
479%%
480%% Handshake utilities
481%%
482
483%%
484%% MD5 hashing
485%%
486
487gen_challenge() ->
488    rand:uniform(1000000).
489
490%% Generate a message digest from Challenge number and Cookie
491gen_digest(Challenge, Cookie) when is_integer(Challenge), is_atom(Cookie) ->
492    C0 = erlang:md5_init(),
493    C1 = erlang:md5_update(C0, atom_to_list(Cookie)),
494    C2 = erlang:md5_update(C1, integer_to_list(Challenge)),
495    binary_to_list(erlang:md5_final(C2)).
496
497
498%%
499%% The differrent stages of the MD5 handshake
500%%
501
502send_status(Socket, Stat) ->
503    case gen_tcp:send(Socket, [$s | atom_to_list(Stat)]) of
504	{error, _} ->
505	    ?shutdown(could_not_send_status);
506	_ ->
507	    true
508    end.
509
510
511recv_status(Socket) ->
512    case gen_tcp:recv(Socket, 0) of
513	{ok, [$s|StrStat]} ->
514	    list_to_atom(StrStat);
515	Bad ->
516	    exit(Bad)
517    end.
518
519send_challenge(Socket, Node, Challenge, Version, GotFlags) ->
520    send_challenge(Socket, Node, Challenge, Version, GotFlags, ?COMPULSORY_DFLAGS).
521
522send_challenge(Socket, Node, Challenge, ?DIST_VER_LOW, _GotFlags, Flags) ->
523    {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
524    ?to_port(Socket, [$n,<<?DIST_VER_LOW:16>>,<<Flags:32>>,
525		      <<Challenge:32>>, atom_to_list(Node)]);
526send_challenge(Socket, Node, Challenge, ?DIST_VER_HIGH, GotFlags, Flags) ->
527    true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
528    {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
529    NodeName = atom_to_list(Node),
530    Nlen = length(NodeName),
531    Creation = erts_internal:get_creation(),
532    ?to_port(Socket, [$N, <<(Flags bor ?DFLAG_HANDSHAKE_23):64>>,
533                      <<Challenge:32>>, <<Creation:32>>,
534                      <<Nlen:16>>, NodeName
535                      ]).
536
537recv_challenge(Socket, OurVersion) ->
538    {ok, Msg} = gen_tcp:recv(Socket, 0),
539    %%io:format("recv_challenge Msg=~p\n", [Msg]),
540    case {OurVersion, Msg} of
541	{?DIST_VER_LOW,
542         [$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
543	    Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
544            true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
545	    Node =list_to_atom(Ns),
546	    ?DIST_VER_LOW = ?u16(V1,V0),
547	    Challenge = ?u32(CA3,CA2,CA1,CA0),
548	    {Node,$n,Challenge};
549
550	{?DIST_VER_HIGH,
551         [$N, F7,F6,F5,F4,F3,F2,F1,F0, CA3,CA2,CA1,CA0,
552              Cr3,Cr2,Cr1,Cr0, NL1,NL0 | Ns]} ->
553	    <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
554            true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
555            <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
556            true = (Creation =/= 0),
557            <<NameLen:16>> = <<NL1,NL0>>,
558            NameLen = length(Ns),
559	    Node = list_to_atom(Ns),
560	    Challenge = ?u32(CA3,CA2,CA1,CA0),
561	    {Node,$N,Challenge};
562
563	_ ->
564	    ?shutdown(no_node)
565    end.
566
567send_complement(Socket, SentNameMsg, ChallengeMsg, OurVersion) ->
568    case {SentNameMsg,ChallengeMsg} of
569        {$n,$N} ->
570            FlagsHigh = our_flags(?COMPULSORY_DFLAGS, OurVersion) bsr 32,
571            ?to_port(Socket, [$c,
572                              <<FlagsHigh:32>>,
573                              ?int32(erts_internal:get_creation())]);
574        {Same,Same} ->
575            ok
576    end.
577
578recv_complement(Socket, $n, OurVersion) when OurVersion > ?DIST_VER_LOW ->
579    case gen_tcp:recv(Socket, 0) of
580	{ok,[$c,Cr3,Cr2,Cr1,Cr0]} ->
581	    Creation = ?u32(Cr3,Cr2,Cr1,Cr0),
582            true = (Creation =/= 0);
583	Err ->
584            {error,Err}
585    end;
586recv_complement(_, _ , _) ->
587    ok.
588
589send_challenge_reply(Socket, Challenge, Digest) ->
590    ?to_port(Socket, [$r,?int32(Challenge),Digest]).
591
592recv_challenge_reply(Socket, ChallengeA, Cookie) ->
593    case gen_tcp:recv(Socket, 0) of
594	{ok,[$r,CB3,CB2,CB1,CB0 | SumB]=Data} when length(SumB) == 16 ->
595	    SumA = gen_digest(ChallengeA, Cookie),
596	    ChallengeB = ?u32(CB3,CB2,CB1,CB0),
597	    if SumB == SumA ->
598		    {ok,ChallengeB};
599	       true ->
600		    {error,Data}
601	    end;
602	Err ->
603            {error,Err}
604    end.
605
606send_challenge_ack(Socket, Digest) ->
607    ?to_port(Socket, [$a,Digest]).
608
609recv_challenge_ack(Socket, ChallengeB, CookieA) ->
610    case gen_tcp:recv(Socket, 0) of
611	{ok,[$a | SumB]} when length(SumB) == 16 ->
612	    SumA = gen_digest(ChallengeB, CookieA),
613	    if SumB == SumA ->
614		    ok;
615	       true ->
616		    ?shutdown(bad_challenge_ack)
617	    end
618    end.
619
620send_name(Socket, MyNode0, OurVersion, AssumedVersion) ->
621    send_name(Socket, MyNode0, OurVersion, AssumedVersion, ?COMPULSORY_DFLAGS).
622
623send_name(Socket, MyNode0, OurVersion, AssumedVersion, Flags) ->
624    MyNode = atom_to_list(MyNode0),
625    if (OurVersion =:= ?DIST_VER_LOW) or
626       (AssumedVersion =:= ?DIST_VER_LOW) ->
627            OurFlags = our_flags(Flags,OurVersion),
628            ok = ?to_port(Socket, [<<$n,OurVersion:16,OurFlags:32>>|MyNode]),
629            $n;
630
631       (OurVersion > ?DIST_VER_LOW) and
632       (AssumedVersion > ?DIST_VER_LOW) ->
633            Creation = erts_internal:get_creation(),
634            NameLen = length(MyNode),
635            ok = ?to_port(Socket, [<<$N, (Flags bor ?DFLAG_HANDSHAKE_23):64,
636                                     Creation:32,NameLen:16>>|MyNode]),
637            $N
638    end.
639
640our_flags(Flags, ?DIST_VER_LOW) ->
641    Flags;
642our_flags(Flags, OurVersion) when OurVersion > ?DIST_VER_LOW ->
643    Flags bor ?DFLAG_HANDSHAKE_23.
644
645recv_name(Socket) ->
646    case gen_tcp:recv(Socket, 0) of
647	{ok,Data} ->
648	    get_name(Data);
649	Res ->
650	    ?shutdown({no_node,Res})
651    end.
652
653get_name([$n, V1,V0, F3,F2,F1,F0 | OtherNode]) ->
654    <<Version:16>> = <<V1,V0>>,
655    5 = Version,
656    <<Flags:32>> = <<F3,F2,F1,F0>>,
657    {list_to_atom(OtherNode), $n, Flags};
658get_name([$N, F7,F6,F5,F4,F3,F2,F1,F0,
659          _C3,_C2,_C1,_C0, NLen1,NLen2 | OtherNode]) ->
660    <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
661    true = (Flags band ?DFLAG_HANDSHAKE_23) =/= 0,
662    <<NameLen:16>> = <<NLen1,NLen2>>,
663    NameLen = length(OtherNode),
664    {list_to_atom(OtherNode), $N, Flags};
665get_name(Data) ->
666    ?shutdown(Data).
667
668%%
669%% The communication with EPMD follows
670%%
671get_epmd_port() ->
672    case init:get_argument(epmd_port) of
673        {ok, [[PortStr|_]|_]} when is_list(PortStr) ->
674            list_to_integer(PortStr);
675        error ->
676            4369 % Default epmd port
677    end.
678
679do_register_node(NodeName, TcpPort, VLow, VHigh) ->
680    case gen_tcp:connect({127,0,0,1}, get_epmd_port(), []) of
681	{ok, Socket} ->
682	    {N0,_} = split(NodeName),
683	    Name = atom_to_list(N0),
684	    Extra = "",
685	    Elen = length(Extra),
686	    Len = 1+2+1+1+2+2+2+length(Name)+2+Elen,
687	    gen_tcp:send(Socket, [?int16(Len), $x,
688				  ?int16(TcpPort),
689				  $M,
690				  0,
691				  ?int16(VHigh),
692				  ?int16(VLow),
693				  ?int16(length(Name)),
694				  Name,
695				  ?int16(Elen),
696				  Extra]),
697	    case wait_for_reg_reply(Socket, []) of
698		{error, epmd_close} ->
699		    exit(epmd_broken);
700		Other ->
701		    Other
702	    end;
703	Error ->
704	    Error
705    end.
706
707wait_for_reg_reply(Socket, SoFar) ->
708    receive
709	{tcp, Socket, Data0} ->
710	    case SoFar ++ Data0 of
711		[$v, Result, A, B, C, D] ->
712		    case Result of
713			0 ->
714			    {alive, Socket, ?u32(A, B, C, D)};
715			_ ->
716			    {error, duplicate_name}
717		    end;
718		[$y, Result, A, B] ->
719		    case Result of
720			0 ->
721			    {alive, Socket, ?u16(A, B)};
722			_ ->
723			    {error, duplicate_name}
724		    end;
725		Data when length(Data) < 4 ->
726		    wait_for_reg_reply(Socket, Data);
727		Garbage ->
728		    {error, {garbage_from_epmd, Garbage}}
729	    end;
730	{tcp_closed, Socket} ->
731	    {error, epmd_close}
732    after 10000 ->
733	    gen_tcp:close(Socket),
734	    {error, no_reg_reply_from_epmd}
735    end.
736
737
738register_node(NodeName, ListenSocket, VLow, VHigh) ->
739    {ok,{_,TcpPort}} = inet:sockname(ListenSocket),
740    case do_register_node(NodeName, TcpPort, VLow, VHigh) of
741	{alive, Socket, _Creation} ->
742	    Socket;
743	Other ->
744	    exit(Other)
745    end.
746
747
748%%
749%% Utilities
750%%
751
752%% Split a nodename
753split([$@|T],A) ->
754    {lists:reverse(A),T};
755split([H|T],A) ->
756    split(T,[H|A]).
757
758split(Atom) ->
759    {A,B} = split(atom_to_list(Atom),[]),
760    {list_to_atom(A),list_to_atom(B)}.
761
762%% Build a distribution message that will make rex answer
763build_rex_message(Cookie,OurName) ->
764    [$?,term_to_binary({6,self(),Cookie,rex}),
765     term_to_binary({'$gen_cast',
766		     {cast,
767		      rpc,
768		      cast,
769		      [OurName, hello, world, []],
770		      self()} })].
771
772%% Receive a distribution message
773recv_message(Socket) ->
774    case gen_tcp:recv(Socket, 0) of
775        {ok,[]} ->
776            recv_message(Socket);  %% a tick, ignore
777	{ok,Data} ->
778	    B0 = list_to_binary(Data),
779	    <<?PASS_THROUGH, B1/binary>> = B0,
780	    {Header,Siz} = binary_to_term(B1,[used]),
781	    <<_:Siz/binary,B2/binary>> = B1,
782	    Message = case (catch binary_to_term(B2)) of
783			  {'EXIT', _} ->
784			      {could_not_digest_message,B2};
785			  Other ->
786			      Other
787		      end,
788	    {Header, Message};
789	Res ->
790	    exit({no_message,Res})
791    end.
792
793%% Build a nodename
794join(Name,Host) ->
795    list_to_atom(atom_to_list(Name) ++ "@" ++ atom_to_list(Host)).
796
797%% start/stop slave.
798start_node(Name, Param) ->
799    test_server:start_node(Name, slave, [{args, Param}]).
800
801stop_node(Node) ->
802    test_server:stop_node(Node).
803
804
805get_nodenames(N, T) ->
806    get_nodenames(N, T, []).
807
808get_nodenames(0, _, Acc) ->
809    Acc;
810get_nodenames(N, T, Acc) ->
811    U = erlang:unique_integer([positive]),
812    get_nodenames(N-1, T, [list_to_atom(?MODULE_STRING
813					++ "-"
814					++ atom_to_list(T)
815					++ "-"
816					++ integer_to_list(U)) | Acc]).
817