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