%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1998-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(epmd_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
% Timeout for test cases (rather long to work on slow machines)
-define(MEDIUM_TEST_TIMEOUT, {minutes,3}).
-define(LONG_TEST_TIMEOUT, {minutes,10}).
% Delay inserted into code
-define(SHORT_PAUSE, 100).
-define(MEDIUM_PAUSE, 1000).
-define(LONG_PAUSE, 5000).
% Information about nodes
-record(node_info, {port, node_type, prot, lvsn, hvsn, node_name, extra}).
% Test server specific exports
-export([all/0, suite/0, groups/0, init_per_testcase/2, end_per_testcase/2]).
-export([register_name/1,
register_name_ipv6/1,
register_names_1/1,
register_names_2/1,
register_duplicate_name/1,
unicode_name/1,
long_unicode_name/1,
get_port_nr/1,
slow_get_port_nr/1,
unregister_others_name_1/1,
unregister_others_name_2/1,
register_overflow/1,
name_with_null_inside/1,
name_null_terminated/1,
stupid_names_req/1,
no_data/1,
one_byte/1,
two_bytes/1,
partial_packet/1,
zero_length/1,
too_large/1,
alive_req_too_small_1/1,
alive_req_too_small_2/1,
alive_req_too_large/1,
returns_valid_empty_extra/1,
returns_valid_populated_extra_with_nulls/1,
names_stdout/1,
buffer_overrun_1/1,
buffer_overrun_2/1,
no_nonlocal_register/1,
no_nonlocal_kill/1,
no_live_killing/1,
socket_reset_before_alive2_reply_is_written/1]).
% Port we use for testing
-define(PORT,2243).
-define(EPMDARGS,"-packet_timeout 1").
-define(DUMMY_PORT, 1000). % Port number to register
% not in real use.
% Timeouts etc inside test cases. Time is in milliseconds.
-define(CONN_RETRY, 4). % Times to retry connecting
-define(CONN_SLEEP, 500).
-define(CONN_TIMEOUT, 100).
-define(RECV_TIMEOUT, 2000).
-define(REG_REPEAT_LIM,1000).
% Message codes in epmd protocol
-define(EPMD_ALIVE2_REQ, $x).
-define(EPMD_ALIVE2_RESP, $y).
-define(EPMD_PORT_PLEASE2_REQ, $z).
-define(EPMD_PORT2_RESP, $w).
-define(EPMD_NAMES_REQ, $n).
-define(EPMD_DUMP_REQ, $d).
-define(EPMD_KILL_REQ, $k).
-define(EPMD_STOP_REQ, $s).
%%
%% all/1
%%
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, ?MEDIUM_TEST_TIMEOUT}].
all() ->
[register_name, register_name_ipv6,
register_names_1, register_names_2,
register_duplicate_name, unicode_name, long_unicode_name,
get_port_nr, slow_get_port_nr,
unregister_others_name_1, unregister_others_name_2,
register_overflow, name_with_null_inside,
name_null_terminated, stupid_names_req, no_data,
one_byte, two_bytes, partial_packet, zero_length,
too_large, alive_req_too_small_1, alive_req_too_small_2,
alive_req_too_large, returns_valid_empty_extra,
returns_valid_populated_extra_with_nulls,
names_stdout,
{group, buffer_overrun}, no_nonlocal_register,
no_nonlocal_kill, no_live_killing,
socket_reset_before_alive2_reply_is_written].
groups() ->
[{buffer_overrun, [],
[buffer_overrun_1, buffer_overrun_2]}].
%%
%% Run before and after each test case
%%
init_per_testcase(_Func, Config) ->
cleanup(),
Config.
end_per_testcase(_Func, _Config) ->
cleanup(),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Register a name
register_name(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = register_node("foobar"),
ok = close(Sock), % Unregister
ok.
%% Register a name over IPv6
register_name_ipv6(Config) when is_list(Config) ->
% Test if the host has an IPv6 loopback address
Res = gen_tcp:listen(0, [inet6, {ip, {0,0,0,0,0,0,0,1}}]),
case Res of
{ok,LSock} ->
gen_tcp:close(LSock),
ok = epmdrun(),
{ok,Sock} = register_node6("foobar6"),
ok = close(Sock), % Unregister
ok;
_Error ->
{skip, "Host does not have an IPv6 loopback address"}
end.
%% Register and unregister two nodes
register_names_1(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock1} = register_node("foobar"),
{ok,Sock2} = register_node("foozap"),
ok = close(Sock1), % Unregister
ok = close(Sock2), % Unregister
ok.
%% Register and unregister two nodes
register_names_2(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock1} = register_node("foobar"),
{ok,Sock2} = register_node("foozap"),
ok = close(Sock2), % Unregister
ok = close(Sock1), % Unregister
ok.
%% Two nodes with the same name
register_duplicate_name(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = register_node("foobar"),
error = register_node("foobar"),
ok = close(Sock), % Unregister
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Check that we can register and lookup a unicode name
unicode_name(Config) when is_list(Config) ->
ok = epmdrun(),
NodeName = [16#1f608],
{ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []),
{ok,NodeInfo} = port_please_v2(NodeName),
NodeName = NodeInfo#node_info.node_name,
ok = close(Sock),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Check that we can register and lookup a long unicode name
long_unicode_name(Config) when is_list(Config) ->
ok = epmdrun(),
BaseChar = 16#1f600,
NodeName = lists:seq(BaseChar, BaseChar+200), % will be 800 bytes long
{ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []),
{ok,NodeInfo} = port_please_v2(NodeName),
NodeName = NodeInfo#node_info.node_name,
ok = close(Sock),
ok.
% Internal function to register a node name, no close, i.e. unregister
register_node(Name) ->
register_node_v2(?DUMMY_PORT,$M,0,5,5,Name,"").
register_node(Name,Port) ->
register_node_v2(Port,$M,0,5,5,Name,"").
register_node6(Name) ->
register_node_v2({0,0,0,0,0,0,0,1},?DUMMY_PORT,$M,0,5,5,Name,"").
register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) ->
register_node_v2("localhost", Port, NodeType, Prot, HVsn, LVsn, Name, Extra).
register_node_v2(Addr, Port, NodeType, Prot, HVsn, LVsn, Name, Extra) ->
Req = alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra),
case send_req(Req, Addr) of
{ok,Sock} ->
case recv(Sock,4) of
{ok, [?EPMD_ALIVE2_RESP,_Res=0,_C0,_C1]} ->
{ok,Sock};
Other ->
io:format("recv on sock ~w: ~p~n", [Sock,Other]),
error
end;
error ->
error
end.
% Internal function to fetch information about a node
port_please_v2(Name) ->
case send_req([?EPMD_PORT_PLEASE2_REQ,
binary_to_list(unicode:characters_to_binary(Name))]) of
{ok,Sock} ->
case recv_until_sock_closes(Sock) of
{ok, Resp} ->
parse_port2_resp(Resp);
Other ->
io:format("recv on sock ~w: ~p~n", [Sock,Other]),
error
end;
error ->
error
end.
parse_port2_resp(Resp) ->
case list_to_binary(Resp) of
<<?EPMD_PORT2_RESP,Res,Port:16,NodeType,Prot,HVsn:16,LVsn:16,
NLen:16,NodeName:NLen/binary,
ELen:16,Extra:ELen/binary>> when Res =:= 0 ->
{ok, #node_info{port=Port,node_type=NodeType,prot=Prot,
hvsn=HVsn,lvsn=LVsn,
node_name=unicode:characters_to_list(NodeName),
extra=binary_to_list(Extra)}};
_Other ->
io:format("invalid port2 resp: ~p~n", [Resp]),
error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Register a name with a null char in it
name_with_null_inside(Config) when is_list(Config) ->
ok = epmdrun(),
error = register_node("foo\000bar"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Register a name with terminating null byte
name_null_terminated(Config) when is_list(Config) ->
ok = epmdrun(),
error = register_node("foobar\000"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Read names from epmd in a stupid way
stupid_names_req(Config) when is_list(Config) ->
ok = epmdrun(),
[FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"),
unregister_many([FirstConn]),
sleep(?MEDIUM_PAUSE),
ok = check_names(Conn),
ok = unregister_many(Conn),
ok.
check_names(Conn) ->
{ok,Sock} = connect_active(),
{ok,Reply} = do_get_names(Sock),
SortConn = lists:sort(Conn),
SortReply = lists:sort(Reply),
ok = check_names_cmp(SortConn, SortReply),
ok.
% Compare if the result was the same as was registered
check_names_cmp([], []) ->
ok;
check_names_cmp([{Name,Port,_Sock} | Conn], [{Name,Port} | Reply]) ->
check_names_cmp(Conn, Reply).
% This code is taken directly from "erl_epmd.erl" in R3A01
-define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]).
-define(u32(X1,X2,X3,X4),
(((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)).
do_get_names(Socket) ->
inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES_REQ]),
receive
{tcp, Socket, [P0,P1,P2,P3 | T]} ->
EpmdPort = ?u32(P0,P1,P2,P3),
if EpmdPort == ?PORT ->
names_loop(Socket, T, []);
true ->
close(Socket),
{error, address}
end;
{tcp_closed, Socket} ->
{ok, []}
end.
names_loop(Socket, Acc, Ps) ->
receive
{tcp, Socket, Bytes} ->
{NAcc, NPs} = scan_names(Acc ++ Bytes, Ps),
names_loop(Socket, NAcc, NPs);
{tcp_closed, Socket} ->
{_, NPs} = scan_names(Acc, Ps), % Really needed?
{ok, NPs}
end.
scan_names(Buf, Ps) ->
case scan_line(Buf, []) of
{Line, NBuf} ->
case parse_line(Line) of
{ok, Entry} ->
scan_names(NBuf, [Entry | Ps]);
error ->
scan_names(NBuf, Ps)
end;
[] -> {Buf, Ps}
end.
scan_line([$\n | Buf], Line) -> {lists:reverse(Line), Buf};
scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]);
scan_line([], _) -> [].
parse_line([$n,$a,$m,$e,$ | Buf0]) ->
case parse_name(Buf0, []) of
{Name, Buf1} ->
case Buf1 of
[$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] ->
case catch list_to_integer(Buf2) of
{'EXIT', _} -> error;
Port -> {ok, {Name, Port}}
end;
_ -> error
end;
error -> error
end;
parse_line(_) -> error.
parse_name([$ | Buf], Name) -> {lists:reverse(Name), Buf};
parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]);
parse_name([], _Name) -> error.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Register a name on a port and ask about port nr
get_port_nr(Config) when is_list(Config) ->
port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]).
%% Register with slow write and ask about port nr
slow_get_port_nr(Config) when is_list(Config) ->
port_request([?EPMD_PORT_PLEASE2_REQ,d,$f,d,$o,d,$o]).
% Internal function used above
port_request(M) ->
ok = epmdrun(),
Port = 1042,
{ok,RSock} = register_node("foo", Port),
{ok,Sock} = connect(),
ok = send(Sock,[size16(M),M]),
case recv_until_sock_closes(Sock) of
{ok, Resp} ->
close(RSock),
{ok,Rec} = parse_port2_resp(Resp),
Port = Rec#node_info.port,
ok;
Other ->
close(RSock),
io:format("recv on sock ~w: ~p~n", [Sock,Other]),
throw({error,Other})
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Unregister name of other node
unregister_others_name_1(Config) when is_list(Config) ->
ok = epmdrun("-relaxed_command_check"),
{ok,RSock} = register_node("foo"),
{ok,Sock} = connect(),
M = [?EPMD_STOP_REQ,"foo"],
ok = send(Sock,[size16(M),M]),
R = "STOPPED",
{ok,R} = recv(Sock,length(R)),
ok = close(RSock),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Unregister name of other node
unregister_others_name_2(Config) when is_list(Config) ->
ok = epmdrun("-relaxed_command_check"),
{ok,Sock} = connect(),
M = [?EPMD_STOP_REQ,"xxx42"],
ok = send(Sock,[size16(M),M]),
R = "NOEXIST",
{ok,R} = recv(Sock,length(R)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Register too many, clean and redo 10 times
register_overflow(Config) when is_list(Config) ->
ct:timetrap(?LONG_TEST_TIMEOUT),
ok = epmdrun(),
Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
Count = length(Conn),
ok = unregister_many(Conn),
sleep(?MEDIUM_PAUSE),
io:format("Limit was ~w names, now reg/unreg all 10 times~n", [Count]),
ok = register_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = rregister_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = register_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = rregister_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = register_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = rregister_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = register_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = rregister_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = register_repeat(Count),
sleep(?MEDIUM_PAUSE),
ok = rregister_repeat(Count),
ok.
register_repeat(Count) ->
Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
ok = unregister_many(Conn),
if
length(Conn) == Count ->
ok;
true ->
error
end.
rregister_repeat(Count) ->
Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
ok = unregister_many(lists:reverse(Conn)),
if
length(Conn) == Count ->
ok;
true ->
error
end.
% Return count of successful registrations
register_many(I, N, _Prefix) when I > N ->
io:format("Done with all ~n", []),
[];
register_many(I, N, Prefix) ->
Name = gen_name(Prefix, I),
Port = ?DUMMY_PORT + I, % Just make it up
case register_node(Name, Port) of
{ok,Sock} ->
[{Name,Port,Sock} | register_many(I + 1, N, Prefix)];
Any ->
test_server:format("Can't register: ~w of 1..~w ~w~n", [Name,N,Any]),
[]
end.
unregister_many([]) ->
ok;
unregister_many([{Name,_Port,Sock} | Socks]) ->
case close(Sock) of
ok ->
unregister_many(Socks);
Any ->
test_server:format("Can't unregister: ~w reason ~w~n", [Name,Any]),
error
end.
gen_name(Str,Int) ->
Str ++ integer_to_list(Int).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Open but send no data
no_data(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
sleep(?LONG_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Send one byte only
one_byte(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
ok = send(Sock,[0]),
sleep(?LONG_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Send packet size only
two_bytes(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
ok = send(Sock,[put16(3)]),
sleep(?LONG_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Got only part of a packet
partial_packet(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
ok = send(Sock,[put16(100),"only a few bytes"]),
sleep(?LONG_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Invalid zero packet size
zero_length(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]),
sleep(?MEDIUM_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Invalid large packet
too_large(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
Size = 63000,
M = lists:duplicate(Size, $z),
ok = send(Sock,[put16(Size),M]),
sleep(?MEDIUM_PAUSE),
% With such a large packet, even the writes can fail as the
% daemon closes before everything is delivered -> econnaborted
case recv(Sock,1) of
closed -> ok;
{error,econnaborted} -> ok;
Other -> exit({unexpected,Other})
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Try to register but not enough data
alive_req_too_small_1(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5),
put16(5),put16(0)],
ok = send(Sock, [size16(M), M]),
sleep(?MEDIUM_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Try to register but not enough data
alive_req_too_small_2(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5),
put16(5)],
ok = send(Sock, [size16(M), M]),
sleep(?MEDIUM_PAUSE),
closed = recv(Sock,1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Try to register but node name too large
alive_req_too_large(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = connect(),
L = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
S = length(lists:flatten(L)),
M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5),
put16(5), put16(S),L,put16(0)],
ok = send(Sock, [size16(M), M]),
sleep(?MEDIUM_PAUSE),
{ok,[?EPMD_ALIVE2_RESP,1]} = recv(Sock,2),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Check that an empty extra is prefixed by a two byte length
returns_valid_empty_extra(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []),
{ok,#node_info{extra=[]}} = port_please_v2("foo"),
ok = close(Sock),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Check a populated extra with embedded null characters
returns_valid_populated_extra_with_nulls(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"),
{ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"),
ok = close(Sock),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test that epmd -names prints registered nodes to stdout
names_stdout(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,Sock} = register_node("foobar"),
ok = epmdrun("-names"),
{ok, Data} = receive {_Port, {data, D}} -> {ok, D}
after 10000 -> {error, timeout}
end,
{match,_} = re:run(Data, "^epmd: up and running", [multiline]),
{match,_} = re:run(Data, "^name foobar at port", [multiline]),
ok = close(Sock),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test security vulnerability in fake extra lengths in alive2_req
buffer_overrun_1(Config) when is_list(Config) ->
ok = epmdrun(),
true = alltrue([hostile(N) || N <- lists:seq(1,10000)]),
ok.
%% Test security vulnerability in fake extra lengths in alive2_req
buffer_overrun_2(Config) when is_list(Config) ->
ok = epmdrun(),
[false | Rest] = [hostile2(N) || N <- lists:seq(255*4,10000)],
true = alltrue(Rest),
ok.
hostile(N) ->
try
Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16>>,
S = size(Bin),
{ok,E}=connect_sturdy(),
gen_tcp:send(E,[<<S:16>>,Bin]),
closed = recv(E,1),
gen_tcp:close(E),
true
catch
_:_ ->
false
end.
hostile2(N) ->
try
B2 = list_to_binary(lists:duplicate(N,255)),
Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16,B2/binary>>,
S = size(Bin),
{ok,E}=connect_sturdy(),
gen_tcp:send(E,[<<S:16>>,Bin]),
Z = recv(E,2),
gen_tcp:close(E),
(Z =:= closed) or (Z =:= {ok, [$y,1]})
catch
_A:_B ->
false
end.
alltrue([]) ->
true;
alltrue([true|T]) ->
alltrue(T);
alltrue([_|_]) ->
false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Ensure that we cannot register throug a nonlocal connection
no_nonlocal_register(Config) when is_list(Config) ->
case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of
{SSH,Name} when is_list(Name), is_list(SSH) ->
do_no_nonlocal_register(Config,Name);
{false,_} ->
{skip, "No ssh command found to create proxy"};
_ ->
{skip, "No ssh_proxy_host configured in ts.config"}
end.
do_no_nonlocal_register(Config,SSHHost) when is_list(Config) ->
ok = epmdrun(),
ProxyPort = proxy_port(),
ok = ssh_proxy(SSHHost,ProxyPort),
Res = try
Name = "gurka_"
%++
%integer_to_list(A1)++"_"++
%integer_to_list(A2)++"_"++
%integer_to_list(A3)++"_"++
%integer_to_list(A4)
,
Bname = list_to_binary(Name),
NameS = byte_size(Bname),
Bin= <<$x:8,4747:16,$M:8,0:8,5:16,
5:16,NameS:16,Bname/binary,
0:16>>,
S = size(Bin),
{ok, E} = connect("localhost",ProxyPort,passive),
gen_tcp:send(E,[<<S:16>>,Bin]),
closed = recv(E,1),
gen_tcp:close(E),
true
catch
_:_ ->
false
end,
%erlang:display(Res),
true = Res,
ok.
%% Ensure that we cannot kill through nonlocal connection
no_nonlocal_kill(Config) when is_list(Config) ->
case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of
{SSH,Name} when is_list(Name), is_list(SSH) ->
do_no_nonlocal_kill(Config,Name);
{false,_} ->
{skip, "No ssh command found to create proxy"};
_ ->
{skip, "No ssh_proxy_host configured in ts.config"}
end.
do_no_nonlocal_kill(Config,SSHHost) when is_list(Config) ->
ok = epmdrun(),
ProxyPort = proxy_port(),
ok = ssh_proxy(SSHHost,ProxyPort),
Res = try
{ok, E} = connect("localhost",ProxyPort,passive),
M = [?EPMD_KILL_REQ],
send(E, [size16(M), M]),
closed = recv(E,2),
gen_tcp:close(E),
sleep(?MEDIUM_PAUSE),
{ok, E2} = connect("localhost",ProxyPort,passive),
gen_tcp:close(E2),
true
catch
_:_ ->
false
end,
%erlang:display(Res),
true = Res,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check
no_live_killing(Config) when is_list(Config) ->
ok = epmdrun(),
{ok,RSock} = register_node("foo"),
{ok,Sock} = connect(),
M = [?EPMD_KILL_REQ],
ok = send(Sock,[size16(M),M]),
{ok,"NO"} = recv(Sock,2),
close(Sock),
{ok,Sock2} = connect(),
M2 = [?EPMD_STOP_REQ,"foo"],
ok = send(Sock2,[size16(M2),M2]),
closed = recv(Sock2,1),
close(Sock2),
close(RSock),
sleep(?MEDIUM_PAUSE),
{ok,Sock3} = connect(),
M3 = [?EPMD_KILL_REQ],
ok = send(Sock3,[size16(M3),M3]),
{ok,"OK"} = recv(Sock3,2),
close(Sock3),
ok.
%% Check for regression - don't make zombie from node which
%% sends TCP RST at wrong time
socket_reset_before_alive2_reply_is_written(Config) when is_list(Config) ->
%% - delay_write for easier triggering of race condition
%% - relaxed_command_check for graceful shutdown of epmd even if there
%% is stuck node.
ok = epmdrun("-delay_write 1 -relaxed_command_check"),
%% We can't use send_req/1 directly as we want to do inet:setopts/2
%% on our socket.
{ok, Sock} = connect(),
%% Issuing close/1 on such socket will result in immediate RST packet.
ok = inet:setopts(Sock, [{linger, {true, 0}}]),
Req = alive2_req(4711, 77, 0, 5, 5, "test", []),
ok = send(Sock, [size16(Req), Req]),
timer:sleep(500), %% Wait for the first 1/2 of delay_write before closing
ok = close(Sock),
timer:sleep(500 + ?SHORT_PAUSE), %% Wait for the other 1/2 of delay_write
%% Wait another delay_write interval, due to delay doubling in epmd.
%% Should be removed when this is issue is fixed there.
timer:sleep(1000),
{ok, SockForNames} = connect_active(),
%% And there should be no stuck nodes
{ok, []} = do_get_names(SockForNames),
ok = close(SockForNames),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Terminate all tests with killing epmd.
cleanup() ->
sleep(?MEDIUM_PAUSE),
case connect() of
{ok,Sock} ->
M = [?EPMD_KILL_REQ],
send(Sock, [size16(M), M]),
recv(Sock,length("OK")),
close(Sock),
sleep(?MEDIUM_PAUSE);
_ ->
true
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start an ssh channel to simulate remote access
proxy_port() ->
?PORT+1.
ssh_proxy(SSHHost,ProxyPort) ->
Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
% Requires proxy to be a unix host with the command 'read' accessible
osrun("ssh -L "++integer_to_list(ProxyPort)++":"++Host++":"
++integer_to_list(?PORT)++" "++SSHHost++" read").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Normal debug start of epmd
epmdrun() ->
epmdrun([]).
epmdrun(Args) ->
case os:find_executable(epmd) of
false ->
{error, {could_not_find_epmd_in_path}};
Path ->
epmdrun(Path,Args)
end.
epmdrun(Epmd,Args0) ->
%% test_server:format("epmdrun() => Epmd = ~p",[Epmd]),
Args = case Args0 of
[] ->
[];
O ->
" "++O
end,
osrun("\"" ++ Epmd ++ "\"" ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT) ++ Args).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start an external process
osrun(Cmd) ->
_ = open_port({spawn, Cmd}, []),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Wrappers of TCP functions
% These functions is the interface for connect.
% Passive mode is the default
connect() ->
connect("localhost",?PORT, passive).
connect(Addr) ->
connect(Addr,?PORT, passive).
connect_active() ->
connect("localhost",?PORT, active).
%% Retry after 15 seconds, to avoid TIME_WAIT socket exhaust.
connect_sturdy() ->
connect("localhost",?PORT, passive, 15000, 3).
% Try a few times before giving up
connect(Addr, Port, Mode) ->
connect(Addr, Port, Mode, ?CONN_SLEEP, ?CONN_RETRY).
connect(Addr, Port, Mode, Sleep, Retry) ->
case connect_repeat(Addr, Retry, Port, Mode, Sleep) of
{ok,Sock} ->
{ok,Sock};
{error,timeout} ->
timeout;
{error,Reason} ->
test_server:format("connect: error: ~w~n",[Reason]),
error;
Any ->
test_server:format("connect: unknown message: ~w~n",[Any]),
exit(1)
end.
% Try a few times before giving up. Pause a small time between
% each try.
connect_repeat(Addr, 1, Port, Mode, _Sleep) ->
connect_mode(Addr,Port, Mode);
connect_repeat(Addr,Retry, Port, Mode, Sleep) ->
case connect_mode(Addr,Port, Mode) of
{ok,Sock} ->
{ok,Sock};
{error,Reason} ->
test_server:format("connect: error: ~w~n",[Reason]),
timer:sleep(Sleep),
connect_repeat(Addr, Retry - 1, Port, Mode, Sleep);
Any ->
test_server:format("connect: unknown message: ~w~n",[Any]),
exit(1)
end.
connect_mode(Addr,Port, active) ->
gen_tcp:connect(Addr, Port, [{packet, 0}], ?CONN_TIMEOUT);
connect_mode(Addr, Port, passive) ->
gen_tcp:connect(Addr, Port, [{packet, 0}, {active, false}],
?CONN_TIMEOUT).
close(Sock) ->
case gen_tcp:close(Sock) of
{error,_} ->
error;
ok ->
ok;
Any ->
test_server:format("unknown message: ~w~n",[Any]),
exit(1)
end.
recv(Sock, Len) ->
recv(Sock, Len, ?RECV_TIMEOUT).
recv(Sock, Len, Timeout) ->
case gen_tcp:recv(Sock, Len, Timeout) of
{ok,[]} -> % Should not be the case
recv(Sock, 1, 1); % any longer
{ok,Data} ->
{ok,Data};
{error,timeout} ->
timeout;
{error,closed} ->
closed;
{error,_}=Error ->
Error;
Any ->
test_server:format("unknown message: ~w~n",[Any]),
exit(1)
end.
%% Send data to socket. The list can be non flat and contain
%% the atom 'd' or tuple {d,Seconds} where this is delay
%% put in between the sent characters.
send(Sock, SendSpec) ->
case send(SendSpec, [], Sock) of
{ok,[]} ->
ok;
{ok,RevBytes} ->
send_direct(Sock, lists:reverse(RevBytes));
Any ->
Any
end.
% If an error, return immediately
% Collect real characters in the first argument to form
% a string to send. Only perform "actions", like a delay,
% when this argument is empty.
send([], RevBytes, _Sock) ->
{ok,RevBytes};
send([Byte | Spec], RevBytes, Sock) when is_integer(Byte) ->
send(Spec, [Byte | RevBytes], Sock);
send([List | Spec], RevBytes, Sock) when is_list(List) ->
case send(List, RevBytes, Sock) of
{ok,Left} ->
send(Spec, Left, Sock);
Other ->
Other
end;
send([d | Spec], RevBytes, Sock) ->
send([{d,1000} | Spec], RevBytes, Sock);
send([{d,S} | Spec], RevBytes, Sock) ->
case send_direct(Sock, lists:reverse(RevBytes)) of
ok ->
timer:sleep(S),
send(Spec, [], Sock);
Any ->
Any
end.
%%%%
send_direct(Sock, Bytes) ->
case gen_tcp:send(Sock, Bytes) of
ok ->
ok;
{error, closed} ->
closed;
{error, _Reason} ->
error;
Any ->
test_server:format("unknown message: ~w~n",[Any]),
Any
end.
send_req(Req) ->
send_req(Req, "localhost").
send_req(Req, Addr) ->
case connect(Addr) of
{ok,Sock} ->
case send(Sock, [size16(Req), Req]) of
ok ->
{ok,Sock};
Other ->
test_server:format("Failed to send ~w on sock ~w: ~w~n",
[Req,Sock,Other]),
error
end;
Other ->
test_server:format("Connect failed when sending ~w: ~p~n",
[Req, Other]),
error
end.
recv_until_sock_closes(Sock) ->
recv_until_sock_closes_2(Sock,[]).
recv_until_sock_closes_2(Sock,AccData) ->
case recv(Sock,0) of
{ok,Data} ->
recv_until_sock_closes_2(Sock,AccData++Data);
closed ->
{ok,AccData};
Other ->
Other
end.
sleep(MilliSeconds) ->
timer:sleep(MilliSeconds).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
put16(N) ->
[N bsr 8, N band 16#ff].
size16(List) ->
N = flat_count(List, 0),
[N bsr 8, N band 16#ff].
flat_count([H|T], N) when is_integer(H) ->
flat_count(T, N+1);
flat_count([H|T], N) when is_list(H) ->
flat_count(T, flat_count(H, N));
flat_count([_|T], N) ->
flat_count(T, N);
flat_count([], N) -> N.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) ->
Utf8Name = unicode:characters_to_binary(Name),
[?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot,
put16(HVsn), put16(LVsn),
put16(size(Utf8Name)), binary_to_list(Utf8Name),
size16(Extra), Extra].