%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2013. 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(epmd_SUITE). -include_lib("test_server/include/test_server.hrl"). -include_lib("kernel/include/file.hrl"). % Timeout for test cases (rather long to work on slow machines) -define(SHORT_TEST_TIMEOUT, ?t:seconds(30)). % Default -define(MEDIUM_TEST_TIMEOUT, ?t:minutes(3)). -define(LONG_TEST_TIMEOUT, ?t:minutes(10)). % Delay inserted into code -define(SHORT_PAUSE, 100). -define(MEDIUM_PAUSE, ?t:seconds(1)). -define(LONG_PAUSE, ?t:seconds(5)). % 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_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2]). -export( [ register_name/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, buffer_overrun_1/1, buffer_overrun_2/1, no_nonlocal_register/1, no_nonlocal_kill/1, no_live_killing/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]}]. all() -> [register_name, 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, {group, buffer_overrun}, no_nonlocal_register, no_nonlocal_kill, no_live_killing]. groups() -> [{buffer_overrun, [], [buffer_overrun_1, buffer_overrun_2]}]. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% %% Run before and after each test case %% init_per_testcase(_Func, Config) -> Dog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), cleanup(), [{watchdog, Dog} | Config]. end_per_testcase(_Func, Config) -> cleanup(), Dog = ?config(watchdog, Config), catch test_server:timetrap_cancel(Dog), % We may have canceled already ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% register_name(doc) -> ["Register a name"]; register_name(suite) -> []; register_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line ok = close(Sock), % Unregister ok. register_names_1(doc) -> ["Register and unregister two nodes"]; register_names_1(suite) -> []; register_names_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), ?line ok = close(Sock1), % Unregister ?line ok = close(Sock2), % Unregister ok. register_names_2(doc) -> ["Register and unregister two nodes"]; register_names_2(suite) -> []; register_names_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), ?line ok = close(Sock2), % Unregister ?line ok = close(Sock1), % Unregister ok. register_duplicate_name(doc) -> ["Two nodes with the same name"]; register_duplicate_name(suite) -> []; register_duplicate_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line error = register_node("foobar"), ?line ok = close(Sock), % Unregister ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unicode_name(doc) -> ["Check that we can register and lookup a unicode name"]; unicode_name(suite) -> []; 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% long_unicode_name(doc) -> ["Check that we can register and lookup a long unicode name"]; long_unicode_name(suite) -> []; 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_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> Utf8Name = unicode:characters_to_binary(Name), Req = [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot, put16(HVsn), put16(LVsn), put16(size(Utf8Name)), binary_to_list(Utf8Name), size16(Extra), Extra], case send_req(Req) of {ok,Sock} -> case recv(Sock,4) of {ok, [?EPMD_ALIVE2_RESP,_Res=0,_C0,_C1]} -> {ok,Sock}; Other -> test_server: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 -> test_server: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 -> test_server:format("invalid port2 resp: ~p~n", [Resp]), error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% name_with_null_inside(doc) -> ["Register a name with a null char in it"]; name_with_null_inside(suite) -> []; name_with_null_inside(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line error = register_node("foo\000bar"), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% name_null_terminated(doc) -> ["Register a name with terminating null byte"]; name_null_terminated(suite) -> []; name_null_terminated(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line error = register_node("foobar\000"), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% stupid_names_req(doc) -> ["Read names from epmd in a stupid way"]; stupid_names_req(suite) -> []; stupid_names_req(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), ?line ok = epmdrun(), ?line [FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"), ?line unregister_many([FirstConn]), sleep(?MEDIUM_PAUSE), ?line ok = check_names(Conn), ?line ok = unregister_many(Conn), test_server:timetrap_cancel(LongDog), ok. check_names(Conn) -> ?line {ok,Sock} = connect_active(), ?line {ok,Reply} = do_get_names(Sock), ?line SortConn = lists:sort(Conn), ?line SortReply = lists:sort(Reply), ?line 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% get_port_nr(doc) -> ["Register a name on a port and ask about port nr"]; get_port_nr(suite) -> []; get_port_nr(Config) when is_list(Config) -> port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]). slow_get_port_nr(doc) -> ["Register with slow write and ask about port nr"]; slow_get_port_nr(suite) -> []; 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) -> ?line ok = epmdrun(), Port = 1042, ?line {ok,RSock} = register_node("foo", Port), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[size16(M),M]), ?line case recv_until_sock_closes(Sock) of {ok, Resp} -> ?line close(RSock), ?line {ok,Rec} = parse_port2_resp(Resp), ?line Port = Rec#node_info.port, ok; Other -> ?line close(RSock), ?line test_server:format("recv on sock ~w: ~p~n", [Sock,Other]), ?line throw({error,Other}) end, ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unregister_others_name_1(doc) -> ["Unregister name of other node"]; unregister_others_name_1(suite) -> []; unregister_others_name_1(Config) when is_list(Config) -> ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,RSock} = register_node("foo"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"foo"], ?line ok = send(Sock,[size16(M),M]), R = "STOPPED", ?line {ok,R} = recv(Sock,length(R)), ?line ok = close(RSock), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unregister_others_name_2(doc) -> ["Unregister name of other node"]; unregister_others_name_2(suite) -> []; unregister_others_name_2(Config) when is_list(Config) -> ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"xxx42"], ?line ok = send(Sock,[size16(M),M]), R = "NOEXIST", ?line {ok,R} = recv(Sock,length(R)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% register_overflow(doc) -> ["Register too many, clean and redo 10 times"]; register_overflow(suite) -> []; register_overflow(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?LONG_TEST_TIMEOUT), ?line ok = epmdrun(), ?line Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), Count = length(Conn), ?line ok = unregister_many(Conn), sleep(?MEDIUM_PAUSE), test_server:format("Limit was ~w names, now reg/unreg all 10 times~n", [Count]), ?line ok = register_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = rregister_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = register_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = rregister_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = register_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = rregister_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = register_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = rregister_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = register_repeat(Count), sleep(?MEDIUM_PAUSE), ?line ok = rregister_repeat(Count), test_server:timetrap_cancel(LongDog), 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 -> test_server: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). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% no_data(doc) -> ["Open but send no data"]; no_data(suite) -> []; no_data(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), sleep(?LONG_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% one_byte(doc) -> ["Send one byte only"]; one_byte(suite) -> []; one_byte(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0]), sleep(?LONG_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% two_bytes(doc) -> ["Send packet size only"]; two_bytes(suite) -> []; two_bytes(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(3)]), sleep(?LONG_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% partial_packet(doc) -> ["Got only part of a packet"]; partial_packet(suite) -> []; partial_packet(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(100),"only a few bytes"]), sleep(?LONG_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% zero_length(doc) -> ["Invalid zero packet size"]; zero_length(suite) -> []; zero_length(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% too_large(doc) -> ["Invalid large packet"]; too_large(suite) -> []; too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), Size = 63000, M = lists:duplicate(Size, $z), ?line 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% alive_req_too_small_1(doc) -> ["Try to register but not enough data"]; alive_req_too_small_1(suite) -> []; alive_req_too_small_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), put16(5),put16(0)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% alive_req_too_small_2(doc) -> ["Try to register but not enough data"]; alive_req_too_small_2(suite) -> []; alive_req_too_small_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), put16(5)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% alive_req_too_large(doc) -> ["Try to register but node name too large"]; alive_req_too_large(suite) -> []; alive_req_too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {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)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line {ok,[?EPMD_ALIVE2_RESP,1]} = recv(Sock,2), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% returns_valid_empty_extra(doc) -> ["Check that an empty extra is prefixed by a two byte length"]; returns_valid_empty_extra(suite) -> []; returns_valid_empty_extra(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []), ?line {ok,#node_info{extra=[]}} = port_please_v2("foo"), ?line ok = close(Sock), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% returns_valid_populated_extra_with_nulls(doc) -> ["Check a populated extra with embedded null characters"]; returns_valid_populated_extra_with_nulls(suite) -> []; returns_valid_populated_extra_with_nulls(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"), ?line {ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"), ?line ok = close(Sock), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% buffer_overrun_1(suite) -> []; buffer_overrun_1(doc) -> ["Test security vulnerability in fake extra lengths in alive2_req"]; buffer_overrun_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line true = alltrue([hostile(N) || N <- lists:seq(1,10000)]), ok. buffer_overrun_2(suite) -> []; buffer_overrun_2(doc) -> ["Test security vulnerability in fake extra lengths in alive2_req"]; buffer_overrun_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line [false | Rest] = [hostile2(N) || N <- lists:seq(255*4,10000)], ?line 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% no_nonlocal_register(suite) -> []; no_nonlocal_register(doc) -> ["Ensure that we cannot register throug a nonlocal connection"]; no_nonlocal_register(Config) when is_list(Config) -> ?line 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) -> ?line ok = epmdrun(), ?line ProxyPort = proxy_port(), ?line ok = ssh_proxy(SSHHost,ProxyPort), Res = try ?line Name = "gurka_" %++ %integer_to_list(A1)++"_"++ %integer_to_list(A2)++"_"++ %integer_to_list(A3)++"_"++ %integer_to_list(A4) , ?line Bname = list_to_binary(Name), ?line NameS = byte_size(Bname), ?line Bin= <<$x:8,4747:16,$M:8,0:8,5:16, 5:16,NameS:16,Bname/binary, 0:16>>, ?line S = size(Bin), ?line {ok, E} = connect("localhost",ProxyPort,passive), ?line gen_tcp:send(E,[<<S:16>>,Bin]), ?line closed = recv(E,1), ?line gen_tcp:close(E), true catch _:_ -> false end, %erlang:display(Res), true = Res, ok. no_nonlocal_kill(suite) -> []; no_nonlocal_kill(doc) -> ["Ensure that we cannot kill through nonlocal connection"]; no_nonlocal_kill(Config) when is_list(Config) -> ?line 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) -> ?line ok = epmdrun(), ?line ProxyPort = proxy_port(), ?line 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% no_live_killing(doc) -> ["Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check"]; no_live_killing(suite) -> []; no_live_killing(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,RSock} = register_node("foo"), ?line {ok,Sock} = connect(), ?line M = [?EPMD_KILL_REQ], ?line ok = send(Sock,[size16(M),M]), ?line {ok,"NO"} = recv(Sock,2), ?line close(Sock), ?line {ok,Sock2} = connect(), ?line M2 = [?EPMD_STOP_REQ,"foo"], ?line ok = send(Sock2,[size16(M2),M2]), ?line closed = recv(Sock2,1), ?line close(Sock2), ?line close(RSock), ?line sleep(?MEDIUM_PAUSE), ?line {ok,Sock3} = connect(), ?line M3 = [?EPMD_KILL_REQ], ?line ok = send(Sock3,[size16(M3),M3]), ?line {ok,"OK"} = recv(Sock3,2), ?line close(Sock3), 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) -> ?line Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")), % Requires proxy to be a unix host with the command 'read' accessible ?line 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 ++ "\"" ++ Args ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % 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) -> case connect() 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.