aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/test/erl_distribution_wb_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/test/erl_distribution_wb_SUITE.erl')
-rw-r--r--lib/kernel/test/erl_distribution_wb_SUITE.erl705
1 files changed, 705 insertions, 0 deletions
diff --git a/lib/kernel/test/erl_distribution_wb_SUITE.erl b/lib/kernel/test/erl_distribution_wb_SUITE.erl
new file mode 100644
index 0000000000..627fed1fdd
--- /dev/null
+++ b/lib/kernel/test/erl_distribution_wb_SUITE.erl
@@ -0,0 +1,705 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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%
+%%
+-module(erl_distribution_wb_SUITE).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+-export([all/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2, whitebox/1,
+ switch_options/1, missing_compulsory_dflags/1]).
+
+%% 1)
+%%
+%% Connections are now always set up symetrically with respect to
+%% publication. If connecting node doesn't send DFLAG_PUBLISHED
+%% the other node wont send DFLAG_PUBLISHED. If the connecting
+%% node send DFLAG_PUBLISHED but the other node doesn't send
+%% DFLAG_PUBLISHED, the connecting node should consider its
+%% DFLAG_PUBLISHED as dropped, i.e the connecting node wont be
+%% published on the other node.
+
+-define(to_port(Socket, Data),
+ case inet_tcp:send(Socket, Data) of
+ {error, closed} ->
+ self() ! {tcp_closed, Socket},
+ {error, closed};
+ R ->
+ R
+ end).
+
+-define(DFLAG_PUBLISHED,1).
+-define(DFLAG_ATOM_CACHE,2).
+-define(DFLAG_EXTENDED_REFERENCES,4).
+-define(DFLAG_DIST_MONITOR,8).
+-define(DFLAG_FUN_TAGS,16#10).
+-define(DFLAG_DIST_MONITOR_NAME,16#20).
+-define(DFLAG_HIDDEN_ATOM_CACHE,16#40).
+-define(DFLAG_NEW_FUN_TAGS,16#80).
+-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
+
+%% From R9 and forward extended references is compulsory
+%% From R10 and forward extended pids and ports are compulsory
+-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor ?DFLAG_EXTENDED_PIDS_PORTS)).
+
+
+-define(shutdown(X), exit(X)).
+-define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]).
+
+-define(int32(X),
+ [((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
+ ((X) bsr 8) band 16#ff, (X) band 16#ff]).
+
+-define(i16(X1,X0),
+ (?u16(X1,X0) -
+ (if (X1) > 127 -> 16#10000; true -> 0 end))).
+
+-define(u16(X1,X0),
+ (((X1) bsl 8) bor (X0))).
+
+-define(u32(X3,X2,X1,X0),
+ (((X3) bsl 24) bor ((X2) bsl 16) bor ((X1) bsl 8) bor (X0))).
+
+all(suite) ->
+ [whitebox,switch_options,missing_compulsory_dflags].
+
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ Dog=?t:timetrap(?t:minutes(1)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog=?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog).
+
+switch_options(doc) ->
+ ["Tests switching of options for the tcp port, as this is done"
+ " when the distribution port is to be shortcut into the emulator."
+ " Maybe this should be in the inet test suite, but only the distribution"
+ " does such horrible things..."];
+switch_options(Config) when is_list(Config) ->
+ ok = test_switch_active(),
+ ok = test_switch_active_partial() ,
+ ok = test_switch_active_and_packet(),
+ ok.
+
+
+whitebox(doc) ->
+ ["Whitebox testing of distribution handshakes. Tests both BC with R5 and "
+ "the md5 version. Note that after R6B, this should be revised to "
+ "remove BC code."];
+whitebox(Config) when is_list(Config) ->
+ ?line {ok, Node} = start_node(?MODULE,""),
+ ?line Cookie = erlang:get_cookie(),
+ ?line {_,Host} = split(node()),
+ ?line ok = pending_up_md5(Node, join(ccc,Host), Cookie),
+ ?line ok = simultaneous_md5(Node, join('A',Host), Cookie),
+ ?line ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host), Cookie),
+ ?line stop_node(Node),
+ ok.
+
+%%
+%% The actual tests
+%%
+
+%%
+%% Switch tcp options test
+%%
+
+test_switch_active() ->
+ ?line {Client, Server} = socket_pair(0, 4),
+ ?line ok = write_packets_32(Client, 1, 5),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 1, 1),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 2, 2),
+ ?line inet:setopts(Server, [{active, true}]),
+ ?line ok = receive_packets(Server, 3, 5),
+ close_pair({Client, Server}),
+ ok.
+
+test_switch_active_partial() ->
+ ?line {Client, Server} = socket_pair(0, 4),
+ ?line ok = write_packets_32(Client, 1, 2),
+ ?line ok = gen_tcp:send(Client,[?int32(4), [0,0,0]]),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 1, 1),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 2, 2),
+ ?line inet:setopts(Server, [{active, true}]),
+ ?line ok = gen_tcp:send(Client,[3]),
+ ?line ok = write_packets_32(Client, 4, 5),
+ ?line ok = receive_packets(Server, 3, 5),
+ close_pair({Client, Server}),
+ ok.
+
+do_test_switch_active_and_packet(SendBefore, SendAfter) ->
+ ?line {Client, Server} = socket_pair(0, 2),
+ ?line ok = write_packets_16(Client, 1, 2),
+ ?line ok = gen_tcp:send(Client,SendBefore),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 1, 1),
+ receive after 2000 -> ok end,
+ ?line ok = read_packets(Server, 2, 2),
+ ?line inet:setopts(Server, [{packet,4}, {active, true}]),
+ ?line ok = gen_tcp:send(Client,SendAfter),
+ ?line ok = write_packets_32(Client, 4, 5),
+ ?line ok = receive_packets(Server, 3, 5),
+ close_pair({Client, Server}),
+ ok.
+
+test_switch_active_and_packet() ->
+ ?line ok = do_test_switch_active_and_packet([0],[0,0,4,0,0,0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0],[0,4,0,0,0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0],[4,0,0,0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0,4],[0,0,0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0,4,0],[0,0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0,4,0,0],[0,3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0,4,0,0,0],[3]),
+ ?line ok = do_test_switch_active_and_packet([0,0,0,4,0,0,0,3],[]),
+ ok.
+
+
+%%
+%% Handshake tests
+%%
+pending_up_md5(Node,OurName,Cookie) ->
+ ?line {NA,NB} = split(Node),
+ ?line {port,PortNo,_} = erl_epmd:port_please(NA,NB),
+ ?line {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ ?line send_name(SocketA,OurName,5),
+ ?line ok = recv_status(SocketA),
+ ?line {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ ?line OurChallengeA = gen_challenge(),
+ ?line OurDigestA = gen_digest(HisChallengeA, Cookie),
+ ?line send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
+ ?line ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
+ %%%
+ %%% OK, one connection is up, now lets be nasty and try another up:
+ %%%
+ %%% But wait for a while, the other node might not have done setnode
+ %%% just yet...
+ ?line receive after 1000 -> ok end,
+ ?line {ok, SocketB} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ ?line send_name(SocketB,OurName,5),
+ ?line alive = recv_status(SocketB),
+ ?line send_status(SocketB, true),
+ ?line gen_tcp:close(SocketA),
+ ?line {hidden,Node,5,HisChallengeB} = recv_challenge(SocketB), % See 1)
+ ?line OurChallengeB = gen_challenge(),
+ ?line OurDigestB = gen_digest(HisChallengeB, Cookie),
+ ?line send_challenge_reply(SocketB, OurChallengeB, OurDigestB),
+ ?line ok = recv_challenge_ack(SocketB, OurChallengeB, Cookie),
+ %%%
+ %%% Well, are we happy?
+ %%%
+
+ ?line inet:setopts(SocketB, [{active, false},
+ {packet, 4}]),
+ ?line gen_tcp:send(SocketB,build_rex_message('',OurName)),
+ ?line {Header, Message} = recv_message(SocketB),
+ ?line io:format("Received header ~p, data ~p.~n",
+ [Header, Message]),
+ ?line gen_tcp:close(SocketB),
+ ok.
+
+simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
+ ?line pong = net_adm:ping(Node),
+ ?line LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
+ {ok, Socket} ->
+ Socket;
+ Else ->
+ exit(Else)
+ end,
+ ?line EpmdSocket = register(OurName, LSocket, 1, 5),
+ ?line {NA, NB} = split(Node),
+ ?line rpc:cast(Node, net_adm, ping, [OurName]),
+ ?line receive after 1000 -> ok end,
+ ?line {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ ?line {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ ?line send_name(SocketA,OurName,5),
+ %% We are still not marked up on the other side, as our first message
+ %% is not sent.
+ ?line SocketB = case gen_tcp:accept(LSocket) of
+ {ok, Socket1} ->
+ ?line Socket1;
+ Else2 ->
+ ?line exit(Else2)
+ end,
+ ?line nok = recv_status(SocketA),
+ % Now we are expected to close A
+ ?line gen_tcp:close(SocketA),
+ % But still Socket B will continue
+ ?line {normal,Node,5} = recv_name(SocketB), % See 1)
+ ?line send_status(SocketB, ok_simultaneous),
+ ?line MyChallengeB = gen_challenge(),
+ ?line send_challenge(SocketB, OurName, MyChallengeB,5),
+ ?line HisChallengeB = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
+ ?line DigestB = gen_digest(HisChallengeB,Cookie),
+ ?line send_challenge_ack(SocketB, DigestB),
+ ?line inet:setopts(SocketB, [{active, false},
+ {packet, 4}]),
+ % This should be the ping message.
+ ?line {Header, Message} = recv_message(SocketB),
+ ?line io:format("Received header ~p, data ~p.~n",
+ [Header, Message]),
+ ?line gen_tcp:close(SocketB),
+ ?line gen_tcp:close(LSocket),
+ ?line gen_tcp:close(EpmdSocket),
+ ok;
+
+simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
+ ?line pong = net_adm:ping(Node),
+ ?line LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
+ {ok, Socket} ->
+ ?line Socket;
+ Else ->
+ ?line exit(Else)
+ end,
+ ?line EpmdSocket = register(OurName, LSocket, 1, 5),
+ ?line {NA, NB} = split(Node),
+ ?line rpc:cast(Node, net_adm, ping, [OurName]),
+ ?line receive after 1000 -> ok end,
+ ?line {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ ?line {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ ?line SocketB = case gen_tcp:accept(LSocket) of
+ {ok, Socket1} ->
+ ?line Socket1;
+ Else2 ->
+ ?line exit(Else2)
+ end,
+ ?line send_name(SocketA,OurName,5),
+ ?line ok_simultaneous = recv_status(SocketA),
+ %% Socket B should die during this
+ ?line case catch begin
+ ?line {normal,Node,5} = recv_name(SocketB), % See 1)
+ ?line send_status(SocketB, ok_simultaneous),
+ ?line MyChallengeB = gen_challenge(),
+ ?line send_challenge(SocketB, OurName, MyChallengeB,
+ 5),
+ ?line HisChallengeB = recv_challenge_reply(
+ SocketB,
+ MyChallengeB,
+ Cookie),
+ ?line DigestB = gen_digest(HisChallengeB,Cookie),
+ ?line send_challenge_ack(SocketB, DigestB),
+ ?line inet:setopts(SocketB, [{active, false},
+ {packet, 4}]),
+ ?line {HeaderB, MessageB} = recv_message(SocketB),
+ ?line io:format("Received header ~p, data ~p.~n",
+ [HeaderB, MessageB])
+ end of
+ {'EXIT', Exitcode} ->
+ ?line io:format("Expected exitsignal caught: ~p.~n",
+ [Exitcode]);
+ Success ->
+ ?line io:format("Unexpected success: ~p~n",
+ [Success]),
+ ?line exit(unexpected_success)
+ end,
+ ?line gen_tcp:close(SocketB),
+ %% But still Socket A will continue
+ ?line {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ ?line OurChallengeA = gen_challenge(),
+ ?line OurDigestA = gen_digest(HisChallengeA, Cookie),
+ ?line send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
+ ?line ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
+
+ ?line inet:setopts(SocketA, [{active, false},
+ {packet, 4}]),
+ ?line gen_tcp:send(SocketA,build_rex_message('',OurName)),
+ ?line {Header, Message} = recv_message(SocketA),
+ ?line io:format("Received header ~p, data ~p.~n",
+ [Header, Message]),
+ ?line gen_tcp:close(SocketA),
+ ?line gen_tcp:close(LSocket),
+ ?line gen_tcp:close(EpmdSocket),
+ ok.
+
+missing_compulsory_dflags(doc) -> [];
+missing_compulsory_dflags(Config) when is_list(Config) ->
+ ?line [Name1, Name2] = get_nodenames(2, missing_compulsory_dflags),
+ ?line {ok, Node} = start_node(Name1,""),
+ ?line {NA,NB} = split(Node),
+ ?line {port,PortNo,_} = erl_epmd:port_please(NA,NB),
+ ?line {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ ?line BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
+ ?line send_name(SocketA,BadNode,5,0),
+ ?line not_allowed = recv_status(SocketA),
+ ?line gen_tcp:close(SocketA),
+ ?line stop_node(Node),
+ ?line ok.
+
+%%
+%% Here comes the utilities
+%%
+
+%%
+%% Switch option utilities
+%%
+write_packets_32(_, M, N) when M > N ->
+ ok;
+write_packets_32(Sock, M, N) ->
+ ok = gen_tcp:send(Sock,[?int32(4), ?int32(M)]),
+ write_packets_32(Sock, M+1, N).
+
+write_packets_16(_, M, N) when M > N ->
+ ok;
+write_packets_16(Sock, M, N) ->
+ ok = gen_tcp:send(Sock,[?int16(4), ?int32(M)]),
+ write_packets_16(Sock, M+1, N).
+
+read_packets(_, M, N) when M > N ->
+ ok;
+read_packets(Sock, M, N) ->
+ Expected = ?int32(M),
+ case gen_tcp:recv(Sock, 0) of
+ {ok, Expected} ->
+ read_packets(Sock, M+1, N);
+ {ok, Unexpected} ->
+ exit({unexpected_data_read, Unexpected});
+ Error ->
+ exit({error_read, Error})
+ end.
+
+receive_packets(Sock, M, N) when M > N ->
+ receive
+ {tcp, Sock, Data} ->
+ exit({extra_data, Data})
+ after 0 ->
+ ok
+ end;
+
+receive_packets(Sock, M, N) ->
+ Expect = ?int32(M),
+ receive
+ {tcp, Sock, Expect} ->
+ receive_packets(Sock, M+1, N);
+ {tcp, Sock, Unexpected} ->
+ exit({unexpected_data_received, Unexpected})
+ after 500 ->
+ exit({no_data_received_for,M})
+ end.
+
+socket_pair(ClientPack, ServerPack) ->
+ {ok, Listen} = gen_tcp:listen(0, [{active, false},
+ {packet, ServerPack}]),
+ {ok, Host} = inet:gethostname(),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} = gen_tcp:connect(Host, Port, [{active, false},
+ {packet, ClientPack}]),
+ {ok, Server} = gen_tcp:accept(Listen),
+ gen_tcp:close(Listen),
+ {Client, Server}.
+
+close_pair({Client, Server}) ->
+ gen_tcp:close(Client),
+ gen_tcp:close(Server),
+ ok.
+
+
+%%
+%% Handshake utilities
+%%
+
+%%
+%% MD5 hashing
+%%
+
+%% This is no proper random number, but that is not really important in
+%% this test
+gen_challenge() ->
+ {_,_,N} = erlang:now(),
+ N.
+
+%% Generate a message digest from Challenge number and Cookie
+gen_digest(Challenge, Cookie) when is_integer(Challenge), is_atom(Cookie) ->
+ C0 = erlang:md5_init(),
+ C1 = erlang:md5_update(C0, atom_to_list(Cookie)),
+ C2 = erlang:md5_update(C1, integer_to_list(Challenge)),
+ binary_to_list(erlang:md5_final(C2)).
+
+
+%%
+%% The differrent stages of the MD5 handshake
+%%
+
+send_status(Socket, Stat) ->
+ case gen_tcp:send(Socket, [$s | atom_to_list(Stat)]) of
+ {error, _} ->
+ ?shutdown(could_not_send_status);
+ _ ->
+ true
+ end.
+
+
+recv_status(Socket) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok, [$s|StrStat]} ->
+ list_to_atom(StrStat);
+ Bad ->
+ exit(Bad)
+ end.
+
+send_challenge(Socket, Node, Challenge, Version) ->
+ send_challenge(Socket, Node, Challenge, Version, ?COMPULSORY_DFLAGS).
+send_challenge(Socket, Node, Challenge, Version, Flags) ->
+ {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
+ ?to_port(Socket, [$n,?int16(Version),?int32(Flags),
+ ?int32(Challenge), atom_to_list(Node)]).
+
+recv_challenge(Socket) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
+ Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
+ Type = case Flags band ?DFLAG_PUBLISHED of
+ 0 ->
+ hidden;
+ _ ->
+ normal
+ end,
+ Node =list_to_atom(Ns),
+ Version = ?u16(V1,V0),
+ Challenge = ?u32(CA3,CA2,CA1,CA0),
+ {Type,Node,Version,Challenge};
+ _ ->
+ ?shutdown(no_node)
+ end.
+
+send_challenge_reply(Socket, Challenge, Digest) ->
+ ?to_port(Socket, [$r,?int32(Challenge),Digest]).
+
+recv_challenge_reply(Socket, ChallengeA, Cookie) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$r,CB3,CB2,CB1,CB0 | SumB]} when length(SumB) == 16 ->
+ SumA = gen_digest(ChallengeA, Cookie),
+ ChallengeB = ?u32(CB3,CB2,CB1,CB0),
+ if SumB == SumA ->
+ ChallengeB;
+ true ->
+ ?shutdown(bad_challenge_reply)
+ end;
+ _ ->
+ ?shutdown(no_node)
+ end.
+
+send_challenge_ack(Socket, Digest) ->
+ ?to_port(Socket, [$a,Digest]).
+
+recv_challenge_ack(Socket, ChallengeB, CookieA) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$a | SumB]} when length(SumB) == 16 ->
+ SumA = gen_digest(ChallengeB, CookieA),
+ if SumB == SumA ->
+ ok;
+ true ->
+ ?shutdown(bad_challenge_ack)
+ end;
+ _ ->
+ ?shutdown(bad_challenge_ack)
+ end.
+
+send_name(Socket, MyNode0, Version) ->
+ send_name(Socket, MyNode0, Version, ?COMPULSORY_DFLAGS).
+send_name(Socket, MyNode0, Version, Flags) ->
+ MyNode = atom_to_list(MyNode0),
+ ok = ?to_port(Socket, [<<$n,Version:16,Flags:32>>|MyNode]).
+
+%%
+%% recv_name is common for both old and new handshake.
+%%
+recv_name(Socket) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,Data} ->
+ get_name(Data);
+ Res ->
+ ?shutdown({no_node,Res})
+ end.
+
+get_name([$m,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
+ {normal, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
+get_name([$h,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
+ {hidden, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
+get_name([$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4 | OtherNode]) ->
+ Type = case ?u32(Flag1, Flag2, Flag3, Flag4) band ?DFLAG_PUBLISHED of
+ 0 ->
+ hidden;
+ _ ->
+ normal
+ end,
+ {Type, list_to_atom(OtherNode),
+ ?u16(VersionA,VersionB)};
+get_name(Data) ->
+ ?shutdown(Data).
+
+%%
+%% The communication with EPMD follows
+%%
+get_epmd_port() ->
+ case init:get_argument(epmd_port) of
+ {ok, [[PortStr|_]|_]} when is_list(PortStr) ->
+ list_to_integer(PortStr);
+ error ->
+ 4369 % Default epmd port
+ end.
+
+do_register_node(NodeName, TcpPort, VLow, VHigh) ->
+ case gen_tcp:connect({127,0,0,1}, get_epmd_port(), []) of
+ {ok, Socket} ->
+ {N0,_} = split(NodeName),
+ Name = atom_to_list(N0),
+ Extra = "",
+ Elen = length(Extra),
+ Len = 1+2+1+1+2+2+2+length(Name)+2+Elen,
+ gen_tcp:send(Socket, [?int16(Len), $x,
+ ?int16(TcpPort),
+ $M,
+ 0,
+ ?int16(VHigh),
+ ?int16(VLow),
+ ?int16(length(Name)),
+ Name,
+ ?int16(Elen),
+ Extra]),
+ case wait_for_reg_reply(Socket, []) of
+ {error, epmd_close} ->
+ exit(epmd_broken);
+ Other ->
+ Other
+ end;
+ Error ->
+ Error
+ end.
+
+wait_for_reg_reply(Socket, SoFar) ->
+ receive
+ {tcp, Socket, Data0} ->
+ case SoFar ++ Data0 of
+ [$y, Result, A, B] ->
+ case Result of
+ 0 ->
+ {alive, Socket, ?u16(A, B)};
+ _ ->
+ {error, duplicate_name}
+ end;
+ Data when length(Data) < 4 ->
+ wait_for_reg_reply(Socket, Data);
+ Garbage ->
+ {error, {garbage_from_epmd, Garbage}}
+ end;
+ {tcp_closed, Socket} ->
+ {error, epmd_close}
+ after 10000 ->
+ gen_tcp:close(Socket),
+ {error, no_reg_reply_from_epmd}
+ end.
+
+
+register(NodeName, ListenSocket, VLow, VHigh) ->
+ {ok,{_,TcpPort}} = inet:sockname(ListenSocket),
+ case do_register_node(NodeName, TcpPort, VLow, VHigh) of
+ {alive, Socket, _Creation} ->
+ Socket;
+ Other ->
+ exit(Other)
+ end.
+
+
+%%
+%% Utilities
+%%
+
+%% Split a nodename
+split([$@|T],A) ->
+ {lists:reverse(A),T};
+split([H|T],A) ->
+ split(T,[H|A]).
+
+split(Atom) ->
+ {A,B} = split(atom_to_list(Atom),[]),
+ {list_to_atom(A),list_to_atom(B)}.
+
+%% Build a distribution message that will make rex answer
+build_rex_message(Cookie,OurName) ->
+ [$?,term_to_binary({6,self(),Cookie,rex}),
+ term_to_binary({'$gen_cast',
+ {cast,
+ rpc,
+ cast,
+ [OurName, hello, world, []],
+ self()} })].
+
+%% Receive a distribution message
+recv_message(Socket) ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,Data} ->
+ B0 = list_to_binary(Data),
+ {_,B1} = erlang:split_binary(B0,1),
+ Header = binary_to_term(B1),
+ Siz = byte_size(term_to_binary(Header)),
+ {_,B2} = erlang:split_binary(B1,Siz),
+ Message = case (catch binary_to_term(B2)) of
+ {'EXIT', _} ->
+ could_not_digest_message;
+ Other ->
+ Other
+ end,
+ {Header, Message};
+ Res ->
+ exit({no_message,Res})
+ end.
+
+%% Build a nodename
+join(Name,Host) ->
+ list_to_atom(atom_to_list(Name) ++ "@" ++ atom_to_list(Host)).
+
+%% start/stop slave.
+start_node(Name, Param) ->
+ ?t:start_node(Name, slave, [{args, Param}]).
+
+stop_node(Node) ->
+ ?t:stop_node(Node).
+
+
+get_nodenames(N, T) ->
+ get_nodenames(N, T, []).
+
+get_nodenames(0, _, Acc) ->
+ Acc;
+get_nodenames(N, T, Acc) ->
+ {A, B, C} = now(),
+ get_nodenames(N-1, T, [list_to_atom(atom_to_list(?MODULE)
+ ++ "-"
+ ++ atom_to_list(T)
+ ++ "-"
+ ++ integer_to_list(A)
+ ++ "-"
+ ++ integer_to_list(B)
+ ++ "-"
+ ++ integer_to_list(C)) | Acc]).