├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── rebar.config ├── src ├── epmd.app.src ├── epmd.erl ├── epmd.hrl ├── epmd_app.erl ├── epmd_listen_sup.erl ├── epmd_reg.erl ├── epmd_srv.erl └── epmd_sup.erl └── test ├── epmd_SUITE.erl └── epmd_erlang_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | epmd 3 | doc/ 4 | *.swp 5 | rebar.lock 6 | rebar3 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: erlang 3 | before_script: 4 | - wget https://s3.amazonaws.com/rebar3/rebar3 5 | - chmod +x rebar3 6 | script: ./rebar3 ct 7 | notifications: 8 | disabled: true 9 | otp_release: 10 | - 18.3 11 | - 18.0 12 | - 17.5 13 | - 17.1 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3 2 | 3 | ifeq ($(wildcard rebar3),rebar3) 4 | REBAR3 = $(CURDIR)/rebar3 5 | endif 6 | 7 | REBAR3 ?= $(shell test -e `which rebar3` 2>/dev/null && which rebar3 || echo "./rebar3") 8 | 9 | ifeq ($(REBAR3),) 10 | REBAR3 = $(CURDIR)/rebar3 11 | endif 12 | 13 | .PHONY: deps test build 14 | 15 | all: build docs 16 | 17 | build: $(REBAR3) 18 | @$(REBAR3) compile 19 | 20 | $(REBAR3): 21 | wget $(REBAR3_URL) || curl -Lo rebar3 $(REBAR3_URL) 22 | chmod a+x rebar3 23 | 24 | deps: 25 | @$(REBAR3) get-deps 26 | 27 | clean: 28 | @$(REBAR3) clean 29 | 30 | distclean: clean 31 | @$(REBAR3) delete-deps 32 | 33 | docs: 34 | @$(REBAR3) edoc 35 | 36 | test: 37 | @$(REBAR3) do ct, cover 38 | 39 | dialyzer: build 40 | @$(REBAR3) dialyzer 41 | 42 | release: test 43 | @$(REBAR3) release 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Erlang Port Mapper Daemon 2 | ========================= 3 | [![Build Status](https://travis-ci.org/erlang/epmd.svg?branch=master)](https://travis-ci.org/erlang/epmd) 4 | 5 | An Erlang implementation of Erlang/OTPs port mapper daemon. 6 | 7 | The Erlang code was originally written by Peer Stritzinger (@peerst) which was 8 | a direct port of Erlang/OTPs C-implementation of EPMD. 9 | 10 | * This code is not blessed for production use just yet. 11 | 12 | This EPMD has two levels of interfaces. The top level being an escript, `epmd` that emulates 13 | the behavior of Erlang/OTP's `epmd`. The escript utilizes the `epmd` application interface. 14 | 15 | ### EPMD Application 16 | 17 | Usage: 18 | 19 | 1> application:start(epmd). 20 | 21 | =INFO REPORT==== 2-Jun-2016::17:04:38 === 22 | EPMD Service started 23 | ok 24 | 25 | #### Environment variables 26 | 27 | The application respects the following erlang environment variables within the epmd scope: 28 | 29 | * `address :: [string()]` - If set, EPMD will listen only on the specified address(es) and the loopback address. 30 | The default behavior is to listen on all available IP addresses. 31 | * `port :: non_neg_integer()` - Identifies which port EPMD will listen to. 32 | Uses OS environment variable `$ERL_EPMD_PORT` if not set or defaults to 4369 if neither is set. 33 | * `relaxed_command_check :: boolean()` - Allows for the epmd to be taken down or forced node unregister by outside influence, 34 | i.e. `epmd -kill` and `epmd -stop` 35 | * `delay_write :: non_neg_integer()` - Simulates a busy server. Delays messages before replying. 36 | 37 | The application respects the following optional OS environment variables: 38 | 39 | * `$ERL_EPMD_ADDRESS` - Expects a comma-separated list of IP addresses. Same behavior as `address` above. 40 | * `$ERL_EPMD_PORT` - Expects an integer. Same behavior as `port` above. 41 | * `$ERL_EPMD_RELAXED_COMMAND_CHECK` - Treated as `true` if set otherwise `false`. Same behavior as `relaxed_command_check`. 42 | 43 | _Note: `$ERL_EPMD_ADDRESS` and `address` is not yet implemented._ 44 | 45 | ### EPMD Escript 46 | 47 | usage: epmd [-d|-debug] [DbgExtra...] [-address List] 48 | [-port No] [-daemon] [-relaxed_command_check] 49 | epmd [-d|-debug] [-port No] [-names|-kill|-stop name] 50 | 51 | See the Erlang epmd manual page for info about the usage. 52 | 53 | Regular options 54 | -address List 55 | Let epmd listen only on the comma-separated list of IP 56 | addresses (and on the loopback interface). 57 | -port No 58 | Let epmd listen to another port than default 4369 59 | -d 60 | -debug 61 | Enable debugging. This will give a log to 62 | the standard error stream. It will shorten 63 | the number of saved used node names to 5. 64 | 65 | If you give more than one debug flag you may 66 | get more debugging information. 67 | -daemon 68 | Start epmd detached (as a daemon) 69 | -relaxed_command_check 70 | Allow this instance of epmd to be killed with 71 | epmd -kill even if there are registered nodes. 72 | Also allows forced unregister (epmd -stop). 73 | 74 | DbgExtra options 75 | -delay_write Seconds 76 | Also a simulation of a busy server. Inserts 77 | a delay before a reply is sent. 78 | 79 | Interactive options 80 | -names 81 | List names registered with the currently running epmd 82 | -kill 83 | Kill the currently running epmd 84 | (only allowed if -names show empty database or 85 | -relaxed_command_check was given when epmd was started). 86 | -stop Name 87 | Forcibly unregisters a name with epmd 88 | (only allowed if -relaxed_command_check was given when 89 | epmd was started). 90 | 91 | 92 | _Note: `-address`, `-debug` and `-daemon` is not yet implemented._ 93 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {cover_enabled, true}. 2 | {escript_incl_apps, []}. 3 | {escript_emu_args, "%%! +sbtu +A0 -noinput\n"}. 4 | {provider_hooks, [{post, [{compile, escriptize}]}]}. 5 | {post_hooks, [{compile, "cp $REBAR_DEPS_DIR/../bin/epmd $REBAR_ROOT_DIR/."}]}. 6 | {deps, []}. 7 | %% vim: ft=erlang 8 | -------------------------------------------------------------------------------- /src/epmd.app.src: -------------------------------------------------------------------------------- 1 | {application, epmd, 2 | [{description, "Erlang Port Mapper Daemon"}, 3 | {vsn, "1.0.0"}, 4 | {modules, [epmd, 5 | epmd_app, 6 | epmd_sup]}, 7 | {registered, [epmd_sup, 8 | epmd, 9 | epmd_listen_sup]}, 10 | {applications, [kernel, 11 | stdlib]}, 12 | {mod, {epmd_app, []}} 13 | ]}. 14 | 15 | %% vim: ft=erlang 16 | -------------------------------------------------------------------------------- /src/epmd.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2016. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | %% 19 | %% 20 | %% File: epmd.erl 21 | %% Author: Björn-Egil Dahlberg 22 | %% Created: 2016-03-12 23 | %% 24 | 25 | -module(epmd). 26 | 27 | -export([main/1]). 28 | 29 | -include("epmd.hrl"). 30 | 31 | main(Args) -> 32 | try parse_args(Args) of 33 | error -> 34 | usage(); 35 | {interactive, names} -> 36 | get_names(); 37 | {interactive, normal} -> 38 | ok = application:start(epmd), 39 | receive after infinity -> ok end 40 | catch 41 | C:E -> 42 | io:format("badness ~w:~w~n~p~n", [C,E,erlang:get_stacktrace()]) 43 | end, 44 | init:stop(). 45 | 46 | parse_args(Args) -> 47 | parse_args(Args, interactive, normal). 48 | %% parse interactive commands 49 | parse_args(["-names"|Args], Type, _Cmd) -> 50 | parse_args(Args, Type, names); 51 | parse_args(["-kill"|Args], Type, _Cmd) -> 52 | parse_args(Args, Type, kill); 53 | parse_args(["-stop", _Name|Args], Type, _Cmd) -> 54 | parse_args(Args, Type, stop); 55 | 56 | %% parse options 57 | parse_args(["-daemon"|Args], _Type, Cmd) -> 58 | application:set_env(epmd, daemon, true), 59 | parse_args(Args, daemon, Cmd); 60 | parse_args(["-relaxed_command_check"|Args], Type, Cmd) -> 61 | application:set_env(epmd, relaxed_command_check, true), 62 | parse_args(Args, Type, Cmd); 63 | parse_args(["-port",PortArg|Args], Type, Cmd) -> 64 | Port = list_to_integer(PortArg), 65 | application:set_env(epmd, port, Port), 66 | parse_args(Args, Type, Cmd); 67 | parse_args(["-address",AddrList|Args], Type, Cmd) -> 68 | Ls = string:tokens(AddrList,","), 69 | F = fun(Str) -> 70 | {ok,Addr} = inet_parse:address(Str), 71 | Addr 72 | end, 73 | Addrs = lists:map(F,Ls), 74 | application:set_env(epmd, address, Addrs), 75 | parse_args(Args, Type, Cmd); 76 | 77 | %% parse debug 78 | parse_args(["-d"|Args], Type, Cmd) -> 79 | application:set_env(epmd, debug, true), 80 | parse_args(Args, Type, Cmd); 81 | parse_args(["-debug"|Args], Type, Cmd) -> 82 | application:set_env(epmd, debug, true), 83 | parse_args(Args, Type, Cmd); 84 | parse_args(["-delay_write",T|Args], Type, Cmd) -> 85 | Time = list_to_integer(T), 86 | application:set_env(epmd, delay_write, Time), 87 | parse_args(Args, Type, Cmd); 88 | parse_args([], Type, Cmd) -> 89 | {Type, Cmd}; 90 | parse_args([A|_], _, _) -> 91 | io:format("unrecognized argument: ~p~n", [A]), 92 | error. 93 | 94 | get_names() -> 95 | Port = get_port(), 96 | case request(get_address(), Port, <>) of 97 | {ok, <<_:32,Names/binary>>} -> 98 | io:format("epmd: up and running on port ~w with data:~n", [Port]), 99 | io:format("~ts", [Names]), 100 | ok; 101 | error -> 102 | io:format(standard_error, "epmd: Cannot connect to ~ts epmd~n", ["local"]), 103 | ok 104 | end. 105 | 106 | get_address() -> 107 | Addr = "0.0.0.0", 108 | Addr. 109 | 110 | get_port() -> 111 | application:get_env(epmd, port, ?EPMD_DEFAULT_PORT). 112 | 113 | request(Addr, Port, Req) -> 114 | request(Addr, Port, Req, 5000). 115 | 116 | request(Addr, Port, Req, Tmo) -> 117 | case gen_tcp:connect(Addr, Port, [binary, 118 | {packet, 2}, 119 | {active, false}], 120 | Tmo) of 121 | {ok, Fd} -> 122 | ok = gen_tcp:send(Fd, Req), 123 | inet:setopts(Fd, [{packet, raw}]), 124 | {ok, Res} = gen_tcp:recv(Fd, 0, Tmo), 125 | ok = gen_tcp:close(Fd), 126 | {ok, Res}; 127 | {error, econnrefused} -> 128 | error 129 | end. 130 | 131 | 132 | %% usage 133 | usage() -> 134 | io:put_chars(usage_string()). 135 | 136 | usage_string() -> 137 | ["usage: epmd [-d|-debug] [DbgExtra...] [-address List]\n", 138 | " [-port No] [-daemon] [-relaxed_command_check]\n", 139 | " epmd [-d|-debug] [-port No] [-names|-kill|-stop name]\n\n", 140 | "See the Erlang epmd manual page for info about the usage.\n\n", 141 | "Regular options\n", 142 | " -address List\n", 143 | " Let epmd listen only on the comma-separated list of IP\n", 144 | " addresses (and on the loopback interface).\n", 145 | " -port No\n", 146 | " Let epmd listen to another port than default ", 147 | integer_to_list(?EPMD_DEFAULT_PORT),"\n", 148 | " -d\n", 149 | " -debug\n", 150 | " Enable debugging. This will give a log to\n", 151 | " the standard error stream. It will shorten\n", 152 | " the number of saved used node names to 5.\n\n", 153 | " If you give more than one debug flag you may\n", 154 | " get more debugging information.\n", 155 | " -daemon\n", 156 | " Start epmd detached (as a daemon)\n", 157 | " -relaxed_command_check\n", 158 | " Allow this instance of epmd to be killed with\n", 159 | " epmd -kill even if there are registered nodes.\n", 160 | " Also allows forced unregister (epmd -stop).\n", 161 | "\nDbgExtra options\n", 162 | " -delay_write Seconds\n", 163 | " Also a simulation of a busy server. Inserts\n", 164 | " a delay before a reply is sent.\n", 165 | "\nInteractive options\n", 166 | " -names\n", 167 | " List names registered with the currently running epmd\n", 168 | " -kill\n", 169 | " Kill the currently running epmd\n", 170 | " (only allowed if -names show empty database or\n", 171 | " -relaxed_command_check was given when epmd was started).\n", 172 | " -stop Name\n", 173 | " Forcibly unregisters a name with epmd\n", 174 | " (only allowed if -relaxed_command_check was given when \n", 175 | " epmd was started).\n"]. 176 | -------------------------------------------------------------------------------- /src/epmd.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1998-2010. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | %% 19 | 20 | -define(EPMD_DEFAULT_PORT, 4369). 21 | 22 | -define(EPMD_ALIVE2_REQ, $x). 23 | -define(EPMD_PORT_PLEASE2_REQ, $z). 24 | -define(EPMD_ALIVE2_RESP, $y). 25 | -define(EPMD_PORT2_RESP, $w). 26 | -define(EPMD_NAMES_REQ, $n). 27 | 28 | %% Commands used only by interactive client 29 | -define(EPMD_DUMP, $d). 30 | -define(EPMD_KILL_REQ, $k). 31 | -define(EPMD_STOP_REQ, $s). 32 | -------------------------------------------------------------------------------- /src/epmd_app.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Peer Stritzinger GmbH 2013-2015. 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 | %% 21 | 22 | -module(epmd_app). 23 | -behaviour(application). 24 | 25 | -export([start/0, start/2, stop/1]). 26 | 27 | start() -> 28 | start(normal, []). 29 | 30 | start(normal, Args) -> 31 | epmd_sup:start_link(Args). 32 | 33 | stop(_State) -> 34 | ok. 35 | -------------------------------------------------------------------------------- /src/epmd_listen_sup.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Peer Stritzinger GmbH 2013-2015. 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 | %% 21 | 22 | -module(epmd_listen_sup). 23 | -behaviour(supervisor). 24 | -include("epmd.hrl"). 25 | 26 | -export([start_link/0, start_listener/0]). 27 | -export([init/1]). 28 | 29 | -define(CHILD(I,Type,Args), {I,{I,start_link,[Args]},temporary,3000,Type,[I]}). 30 | 31 | start_link() -> 32 | supervisor:start_link({local, ?MODULE}, ?MODULE, []), 33 | start_listener(). 34 | 35 | start_listener() -> 36 | supervisor:start_child(?MODULE, []). 37 | 38 | init([]) -> 39 | PortNo = get_port_no(), 40 | {ok, Listen} = gen_tcp:listen(PortNo, [{active, once}, 41 | binary, inet6, 42 | {reuseaddr, true}, 43 | {packet, 2}]), 44 | {ok, {{simple_one_for_one, 60, 3600}, 45 | [?CHILD(epmd_srv, worker, 46 | [Listen,PortNo, 47 | %% debug options 48 | application:get_env(epmd, relaxed_command_check, false), 49 | application:get_env(epmd, delay_write, 0)])]}}. 50 | 51 | get_port_no() -> 52 | case os:getenv("ERL_EPMD_PORT") of 53 | false -> application:get_env(epmd, port, ?EPMD_DEFAULT_PORT); 54 | Port -> 55 | case (catch list_to_integer(Port)) of 56 | N when is_integer(N) -> N; 57 | _ -> application:get_env(epmd, port, ?EPMD_DEFAULT_PORT) 58 | end 59 | end. 60 | -------------------------------------------------------------------------------- /src/epmd_reg.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Peer Stritzinger GmbH 2013-2015. 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 | %% 21 | 22 | -module(epmd_reg). 23 | -behaviour(gen_server). 24 | 25 | -export([start_link/0, node_reg/7, node_unreg/1, lookup/1, nodes/0]). 26 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 27 | code_change/3, terminate/2]). 28 | 29 | -type creation() :: 1..3. 30 | -type name() :: binary(). 31 | 32 | -record(node, {symname :: name(), 33 | port :: inet:port_number(), 34 | nodetype :: byte(), %% specify 35 | protocol :: byte(), %% specify 36 | highvsn :: 0..65535, 37 | lowvsn :: 0..65535, 38 | extra :: binary(), 39 | creation :: creation(), 40 | monref :: reference()}). 41 | 42 | -record(state, {reg = [] :: [#node{}], 43 | unreg = [] :: [#node{}], 44 | unreg_count = 0 :: non_neg_integer()}). 45 | 46 | -define(max_unreg_count, 1000). 47 | 48 | start_link() -> 49 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 50 | 51 | -spec node_reg(Name, Port, Nodetype, Protocol, Highvsn, Lowvsn, Extra) -> 52 | {'ok', creation()} | {'error','name_occupied'} when 53 | Name :: name(), 54 | Port :: inet:port_number(), 55 | Nodetype :: byte(), 56 | Protocol :: byte(), 57 | Highvsn :: 0..65535, 58 | Lowvsn :: 0..65535, 59 | Extra :: binary(). 60 | 61 | node_reg(Name, Port, Nodetype, Protocol, Highvsn, Lowvsn, Extra) -> 62 | gen_server:call(?MODULE, {node_reg, Name, Port, Nodetype, 63 | Protocol, Highvsn, Lowvsn, Extra, self()}). 64 | 65 | -spec node_unreg(Name :: name()) -> 'ok' | 'error'. 66 | 67 | node_unreg(Name) -> 68 | gen_server:call(?MODULE, {node_unreg, Name}). 69 | 70 | -spec lookup(Name :: binary()) -> Result when 71 | Result :: {'ok', Name, Port, Nodetype, Protocol, Highvsn, Lowvsn, Extra} 72 | | {'error','not_found'}, 73 | Name :: binary(), 74 | Port :: inet:port_number(), 75 | Nodetype :: byte(), 76 | Protocol :: byte(), 77 | Highvsn :: 0..65535, 78 | Lowvsn :: 0..65535, 79 | Extra :: binary(). 80 | 81 | lookup(Name) -> 82 | gen_server:call(?MODULE, {lookup, Name}). 83 | 84 | -spec nodes() -> [{name(),inet:port_number()}]. 85 | 86 | nodes() -> 87 | gen_server:call(?MODULE, nodes). 88 | 89 | init([]) -> 90 | {ok, #state{}}. 91 | 92 | handle_call({node_reg, Name, Port, Nodetype, Protocol, Highvsn, Lowvsn, 93 | Extra, Srv_pid}, _From, 94 | #state{reg=Reg, unreg=Unreg, unreg_count=Uc}=State) -> 95 | case lists:keyfind(Name, #node.symname, Reg) of 96 | #node{} -> {reply, {error, name_occupied}, State}; 97 | false -> 98 | Mref = monitor(process, Srv_pid), 99 | %% if the following doesn't make sense: I'm just trying to emulate 100 | %% what the original epmd does 101 | case lists:keytake(Name, #node.symname, Unreg) of 102 | {value, #node{creation=Cr}, New_unreg} -> 103 | Creation = (Cr rem 3) + 1, 104 | New_unreg_count = Uc-1; 105 | false -> 106 | Creation = (time_seconds() rem 3) + 1, 107 | New_unreg = Unreg, 108 | New_unreg_count = Uc 109 | end, 110 | Node = #node{symname=Name, port=Port, nodetype=Nodetype, 111 | protocol=Protocol, highvsn=Highvsn, lowvsn=Lowvsn, 112 | extra=Extra, creation=Creation, monref=Mref}, 113 | {reply, {ok, Creation}, 114 | State#state{reg=[Node|Reg], 115 | unreg=New_unreg, unreg_count=New_unreg_count}} 116 | end; 117 | handle_call({lookup, Name}, _From, #state{reg=Reg}=State) -> 118 | case lists:keyfind(Name, #node.symname, Reg) of 119 | #node{symname=Name, port=Port, nodetype=Nodetype, 120 | protocol=Protocol, highvsn=Highvsn, lowvsn=Lowvsn, 121 | extra=Extra} -> 122 | {reply, {ok, Name, Port, Nodetype, Protocol, 123 | Highvsn, Lowvsn, Extra}, State}; 124 | false -> 125 | {reply, {error, notfound}, State} 126 | end; 127 | handle_call(nodes, _From, #state{reg=Reg}=State) -> 128 | {reply,[{Name,Port}||#node{symname=Name,port=Port}<-Reg],State}; 129 | handle_call({node_unreg, Name}, _From, #state{reg=Reg,unreg=Unreg,unreg_count=Uc}=S) -> 130 | case lists:keytake(Name, #node.symname, Reg) of 131 | {value, Node, New_reg} -> 132 | error_logger:info_msg("epmd: unregistering '~ts:~w' on port ~w~n", 133 | [safe_string(Node#node.symname), 134 | Node#node.creation, 135 | Node#node.port]), 136 | S1 = S#state{reg=New_reg, unreg=[Node|Unreg], unreg_count=Uc+1}, 137 | {reply, ok, S1}; 138 | _ -> 139 | {reply, error, S} 140 | end. 141 | 142 | handle_cast(_, State) -> 143 | {noreply, State}. 144 | 145 | handle_info({'DOWN', Mref, _, _, _}, 146 | #state{reg=Reg, unreg=Unreg, unreg_count=Uc}=State) 147 | when Uc < 2*?max_unreg_count -> 148 | {value, Node, New_reg} = lists:keytake(Mref, #node.monref, Reg), 149 | error_logger:info_msg("epmd: unregistering '~ts:~w' on port ~w~n", 150 | [safe_string(Node#node.symname), 151 | Node#node.creation, 152 | Node#node.port]), 153 | {noreply, State#state{reg=New_reg, unreg=[Node|Unreg], 154 | unreg_count=Uc+1}}; 155 | handle_info({'DOWN', Mref, _, _, _}, 156 | #state{reg=Reg, unreg=Unreg, unreg_count=Uc}=State) 157 | when Uc =:= 2*?max_unreg_count -> 158 | {value, Node, New_reg} = lists:keytake(Mref, #node.monref, Reg), 159 | error_logger:info_msg("epmd: unregistering '~ts:~w' on port ~w~n", 160 | [safe_string(Node#node.symname), 161 | Node#node.creation, 162 | Node#node.port]), 163 | {Ur, _} = lists:split(?max_unreg_count, Unreg), 164 | {noreply, State#state{reg=New_reg, unreg=[Node|Ur], 165 | unreg_count=?max_unreg_count+1}}; 166 | handle_info(_Info, State) -> 167 | {noreply, State}. 168 | 169 | terminate(_Reason, _State) -> 170 | ok. 171 | 172 | code_change(_OldVsn, State, _Extra) -> 173 | {ok, State}. 174 | 175 | time_seconds() -> 176 | {_, Sec, _} = os:timestamp(), 177 | Sec. 178 | 179 | safe_string(<>) -> 180 | <>; 181 | safe_string(Name) -> 182 | Name. 183 | -------------------------------------------------------------------------------- /src/epmd_srv.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Peer Stritzinger GmbH 2013-2015. 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 | %% 21 | 22 | -module(epmd_srv). 23 | -behaviour(gen_server). 24 | 25 | -define(maxsymlen, (255*4)). 26 | 27 | -record(env, {socket :: gen_tcp:socket(), 28 | port :: non_neg_integer(), 29 | %% debug settings 30 | relaxed = false :: boolean(), 31 | delay_write = 0 :: non_neg_integer()}). 32 | 33 | -export([start_link/1]). 34 | -export([init/1, terminate/2, 35 | handle_call/3, handle_cast/2, 36 | handle_info/2, code_change/3]). 37 | 38 | -include("epmd.hrl"). 39 | 40 | start_link([Socket,PortNo,Relaxed,DelayWrite]) -> 41 | gen_server:start_link(?MODULE, [Socket,PortNo,Relaxed,DelayWrite], []). 42 | 43 | init([S,Port,Relaxed,DelayWrite]) -> 44 | gen_server:cast(self(), accept), 45 | {ok, #env{socket=S,port=Port, 46 | relaxed=Relaxed, 47 | delay_write=DelayWrite}}. 48 | 49 | handle_call(_Req, _From, Env) -> 50 | {noreply, Env}. 51 | 52 | handle_cast(accept, #env{socket=Listen}=Env) -> 53 | {ok, Accept} = gen_tcp:accept(Listen), 54 | epmd_listen_sup:start_listener(), 55 | {noreply, Env#env{socket=Accept}}. 56 | 57 | handle_info({tcp, S, <>}, 59 | #env{socket=S}=Env) -> 60 | case {name_is_valid(Name),extra_is_valid(Extra)} of 61 | {ok,ok} -> 62 | case epmd_reg:node_reg(Name, Port, Type, Protocol, High, Low, Extra) of 63 | {ok, Creation} -> 64 | error_logger:info_msg("epmd - ALIVE2_REQ: registering '~ts:~w' on port ~w~n", [safe_string(Name), 65 | Creation, Port]), 66 | reply(S, <>, Env); 67 | {error, _} -> 68 | error_logger:info_msg("epmd - ALIVE2_REQ: failed to register '~ts'~n", [safe_string(Name)]), 69 | reply(S, <>, Env) 70 | end, 71 | {noreply, Env}; 72 | {invalid_string,_} -> 73 | error_logger:info_msg("epmd - ALIVE2_REQ: invalid name (nulls)~n"), 74 | gen_tcp:close(S), 75 | {stop, normal, Env}; 76 | {NameValid,ExtraValid} when NameValid =:= invalid_size orelse 77 | ExtraValid =:= invalid -> 78 | %% really? .. this the way? 79 | error_logger:info_msg("epmd - ALIVE2_REQ: invalid size (too large)~n"), 80 | reply(S, <>, Env), 81 | {noreply, Env} 82 | end; 83 | handle_info({tcp, S, <>}, #env{socket=S}=Env) -> 84 | %% why do we handle too large and too small differently? 85 | error_logger:info_msg("epmd - ALIVE2_REQ: invalid size (too small)~n"), 86 | gen_tcp:close(S), 87 | {stop, normal, Env}; 88 | handle_info({tcp, S, <>}, #env{socket=S}=Env) when byte_size(Name) =< ?maxsymlen -> 89 | case epmd_reg:lookup(Name) of 90 | {ok, Name, Port, Nodetype, Protocol, Highvsn, Lowvsn, Extra} -> 91 | error_logger:info_msg("epmd - PORT_PLEASE2_REQ: '~ts' is registered on port ~w~n", [safe_string(Name), Port]), 92 | Len = byte_size(Name), 93 | Elen = byte_size(Extra), 94 | reply(S, <>, Env); 97 | {error, _} -> 98 | error_logger:info_msg("epmd - PORT_PLEASE2_REQ: '~ts' is not registered~n", [safe_string(Name)]), 99 | reply(S, <>, Env) 100 | end, 101 | gen_tcp:close(S), 102 | {stop, normal, Env}; 103 | handle_info({tcp, S, <>}, #env{socket=S}=Env) -> 104 | error_logger:info_msg("epmd - PORT_PLEASE2_REQ: invalid name size (too large)~n"), 105 | gen_tcp:close(S), 106 | {stop, normal, Env}; 107 | handle_info({tcp, S, <>}, #env{port=ServerPort, socket=S}=Env) -> 108 | error_logger:info_msg("epmd - NAMES_REQ~n"), 109 | Format = "name ~s at port ~w~n", 110 | Nodes = iolist_to_binary([io_lib:format(Format, [Name, Port]) || 111 | {Name, Port} <- epmd_reg:nodes()]), 112 | reply(S, <>, Env), 113 | gen_tcp:close(S), 114 | {stop, normal,Env}; 115 | handle_info({tcp, S, <>}, #env{relaxed=true,socket=S}=Env) -> 116 | error_logger:info_msg("epmd - STOP_REQ: stop request from '~ts'~n", [safe_string(Name)]), 117 | %% Check if local peer 118 | %% Check if STOP_REQ is allowed 119 | case epmd_reg:node_unreg(Name) of 120 | ok -> reply(S, <<"STOPPED">>, Env); 121 | error -> reply(S, <<"NOEXIST">>, Env) 122 | end, 123 | gen_tcp:close(S), 124 | {stop, normal,Env}; 125 | handle_info({tcp, S, <>}, #env{socket=S}=Env) -> 126 | error_logger:info_msg("epmd - STOP_REQ: stop request from '~ts' DISALLOWED~n", [safe_string(Name)]), 127 | gen_tcp:close(S), 128 | {stop, normal,Env}; 129 | handle_info({tcp, S, <>}, #env{relaxed=true,socket=S}=Env) -> 130 | error_logger:info_msg("epmd - KILL_REQ: allowed (relaxed)~n"), 131 | %% Check if local peer 132 | reply(S, <<"OK">>, Env), 133 | erlang:halt(), 134 | gen_tcp:close(S), 135 | {stop, normal, Env}; 136 | handle_info({tcp, S, <>}, #env{socket=S}=Env) -> 137 | case epmd_reg:nodes() of 138 | [] -> 139 | error_logger:info_msg("epmd - KILL_REQ: allowed (no live nodes)~n"), 140 | %% Check if local peer 141 | reply(S, <<"OK">>, Env), 142 | erlang:halt(); 143 | _ -> 144 | error_logger:info_msg("epmd - KILL_REQ: DISALLOWED (live nodes)~n"), 145 | reply(S, <<"NO">>, Env) 146 | end, 147 | gen_tcp:close(S), 148 | {stop, normal,Env}; 149 | handle_info({tcp, S, <<>>}, #env{socket=S}=Env) -> 150 | gen_tcp:close(S), 151 | {stop, normal, Env}; 152 | handle_info({tcp_closed, S}, #env{socket=S}=Env) -> 153 | {stop, normal, Env}; 154 | handle_info(Info, Env) -> 155 | error_logger:error_msg("epmd - unrecognized request: ~p~n", [Info]), 156 | {noreply, Env}. 157 | 158 | terminate(_Reason, _Env) -> 159 | ok. 160 | 161 | code_change(_OldVsn, Env, _Extra) -> 162 | {ok, Env}. 163 | 164 | reply(S, Data, #env{delay_write=Delay}) -> 165 | sleep_seconds(Delay), %% debug 166 | %% we need {packet, 2} on receive but raw on send 167 | inet:setopts(S, [{packet, raw}]), 168 | gen_tcp:send(S, Data), 169 | inet:setopts(S, [{packet, 2}, {active, once}]). 170 | 171 | 172 | safe_string(<>) -> 173 | <>; 174 | safe_string(Name) -> 175 | Name. 176 | 177 | %% check for valid name 178 | %% - valid utf8 179 | %% - no nulls 180 | %% - total length no more than maxsymlen 181 | %% - total characters no more than 255 182 | name_is_valid(Name) when byte_size(Name) > ?maxsymlen -> 183 | invalid_size; 184 | name_is_valid(Name) when is_binary(Name) -> 185 | Ls = unicode:characters_to_list(Name), 186 | name_is_valid(Ls, 0). 187 | 188 | name_is_valid(_, N) when N > 255 -> 189 | invalid_size; 190 | name_is_valid([], _) -> 191 | ok; 192 | name_is_valid([0|_], _) -> 193 | invalid_string; 194 | name_is_valid([_|Ls], N) -> 195 | name_is_valid(Ls, N + 1). 196 | 197 | extra_is_valid(Extra) when byte_size(Extra) > ?maxsymlen -> 198 | invalid; 199 | extra_is_valid(_) -> 200 | ok. 201 | 202 | sleep_seconds(0) -> 203 | ok; 204 | sleep_seconds(Time) -> 205 | timer:sleep(Time*1000). 206 | -------------------------------------------------------------------------------- /src/epmd_sup.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Peer Stritzinger GmbH 2013-2015. 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 | %% 21 | 22 | -module(epmd_sup). 23 | -behaviour(supervisor). 24 | 25 | %% API 26 | -export([start_link/1, get_childspecs/0]). 27 | 28 | %% supervisor callbacks 29 | -export([init/1]). 30 | 31 | start_link(Args) -> 32 | supervisor:start_link({local, ?MODULE}, ?MODULE, Args). 33 | 34 | init(_Args) -> 35 | error_logger:info_msg("epmd - service started~n"), 36 | %% Hint: 37 | %% Child_spec = {Name, {M, F, A}, 38 | %% Restart, Shutdown_time, Type, Modules_used} 39 | {ok, {{one_for_one, 3, 60}, get_childspecs()}}. 40 | 41 | get_childspecs() -> 42 | [{epmd_reg, 43 | {epmd_reg, start_link, []}, 44 | permanent, 45 | 5000, 46 | worker, 47 | [epmd_reg]}, 48 | {epmd_listen_sup, 49 | {epmd_listen_sup, start_link, []}, 50 | permanent, 51 | 5000, 52 | supervisor, 53 | [epmd_listen_sup]} 54 | ]. 55 | -------------------------------------------------------------------------------- /test/epmd_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1998-2013. 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(epmd_SUITE). 21 | 22 | -include_lib("common_test/include/ct.hrl"). 23 | -include_lib("kernel/include/file.hrl"). 24 | 25 | % Timeout for test cases (rather long to work on slow machines) 26 | 27 | % Delay inserted into code [ms] 28 | -define(SHORT_PAUSE, 100). 29 | -define(MEDIUM_PAUSE, 300). 30 | -define(LONG_PAUSE, 500). 31 | -define(EPMD, "../../bin/epmd"). 32 | 33 | % Information about nodes 34 | -record(node_info, {port, node_type, prot, lvsn, hvsn, node_name, extra}). 35 | 36 | % Test server specific exports 37 | -export([all/0, suite/0, groups/0, 38 | init_per_testcase/2, end_per_testcase/2]). 39 | 40 | -export([register_name/1, 41 | register_name_ipv6/1, 42 | register_names_1/1, 43 | register_names_2/1, 44 | register_duplicate_name/1, 45 | unicode_name/1, 46 | long_unicode_name/1, 47 | get_port_nr/1, 48 | slow_get_port_nr/1, 49 | unregister_others_name_1/1, 50 | unregister_others_name_2/1, 51 | register_overflow/1, 52 | name_with_null_inside/1, 53 | name_null_terminated/1, 54 | stupid_names_req/1, 55 | 56 | no_data/1, 57 | one_byte/1, 58 | two_bytes/1, 59 | partial_packet/1, 60 | zero_length/1, 61 | too_large/1, 62 | alive_req_too_small_1/1, 63 | alive_req_too_small_2/1, 64 | alive_req_too_large/1, 65 | 66 | returns_valid_empty_extra/1, 67 | returns_valid_populated_extra_with_nulls/1, 68 | 69 | names_stdout/1, 70 | 71 | buffer_overrun_1/1, 72 | buffer_overrun_2/1, 73 | no_nonlocal_register/1, 74 | no_nonlocal_kill/1, 75 | no_live_killing/1, 76 | 77 | socket_reset_before_alive2_reply_is_written/1]). 78 | 79 | 80 | % Port we use for testing 81 | -define(PORT,2243). 82 | -define(EPMDARGS,""). 83 | %-define(EPMDARGS,"-packet_timeout 1"). %% packet timeout is not implemented 84 | 85 | -define(DUMMY_PORT, 1000). % Port number to register 86 | % not in real use. 87 | 88 | % Timeouts etc inside test cases. Time is in milliseconds. 89 | -define(CONN_RETRY, 4). % Times to retry connecting 90 | -define(CONN_SLEEP, 50). % Inital, doubles every retry 91 | -define(CONN_TIMEOUT, 100). 92 | -define(RECV_TIMEOUT, 2000). 93 | -define(REG_REPEAT_LIM,1000). 94 | 95 | % Message codes in epmd protocol 96 | -define(EPMD_ALIVE2_REQ, $x). 97 | -define(EPMD_ALIVE2_RESP, $y). 98 | -define(EPMD_PORT_PLEASE2_REQ, $z). 99 | -define(EPMD_PORT2_RESP, $w). 100 | -define(EPMD_NAMES_REQ, $n). 101 | -define(EPMD_DUMP_REQ, $d). 102 | -define(EPMD_KILL_REQ, $k). 103 | -define(EPMD_STOP_REQ, $s). 104 | 105 | %% 106 | %% all/1 107 | %% 108 | 109 | suite() -> 110 | [{timetrap, {minutes, 3}}]. 111 | 112 | all() -> 113 | [register_name, register_name_ipv6, 114 | register_names_1, register_names_2, 115 | register_duplicate_name, unicode_name, long_unicode_name, 116 | get_port_nr, slow_get_port_nr, 117 | unregister_others_name_1, unregister_others_name_2, 118 | register_overflow, name_with_null_inside, 119 | name_null_terminated, stupid_names_req, no_data, 120 | one_byte, two_bytes, partial_packet, zero_length, 121 | too_large, alive_req_too_small_1, alive_req_too_small_2, 122 | alive_req_too_large, returns_valid_empty_extra, 123 | returns_valid_populated_extra_with_nulls, 124 | names_stdout, 125 | {group, buffer_overrun}, no_nonlocal_register, 126 | no_nonlocal_kill, no_live_killing, 127 | socket_reset_before_alive2_reply_is_written]. 128 | 129 | groups() -> 130 | [{buffer_overrun, [], 131 | [buffer_overrun_1, buffer_overrun_2]}]. 132 | 133 | %% 134 | %% Run before and after each test case 135 | %% 136 | 137 | init_per_testcase(_Func, Config) -> 138 | cleanup(), 139 | Config. 140 | 141 | end_per_testcase(_Func, _Config) -> 142 | cleanup(), 143 | ok. 144 | 145 | 146 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 147 | 148 | 149 | %% Register a name 150 | register_name(Config) when is_list(Config) -> 151 | ok = epmdrun(), 152 | {ok,Sock} = register_node("foobar"), 153 | ok = close(Sock), % Unregister 154 | ok. 155 | 156 | %% Register and unregister two nodes 157 | register_names_1(Config) when is_list(Config) -> 158 | ok = epmdrun(), 159 | {ok,Sock1} = register_node("foobar"), 160 | {ok,Sock2} = register_node("foozap"), 161 | ok = close(Sock1), % Unregister 162 | ok = close(Sock2), % Unregister 163 | ok. 164 | 165 | %% Register and unregister two nodes 166 | register_names_2(Config) when is_list(Config) -> 167 | ok = epmdrun(), 168 | {ok,Sock1} = register_node("foobar"), 169 | {ok,Sock2} = register_node("foozap"), 170 | ok = close(Sock2), % Unregister 171 | ok = close(Sock1), % Unregister 172 | ok. 173 | 174 | %% Two nodes with the same name 175 | register_duplicate_name(Config) when is_list(Config) -> 176 | ok = epmdrun(), 177 | {ok,Sock} = register_node("foobar"), 178 | error = register_node("foobar"), 179 | ok = close(Sock), % Unregister 180 | ok. 181 | 182 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 183 | 184 | %% Check that we can register and lookup a unicode name 185 | unicode_name(Config) when is_list(Config) -> 186 | ok = epmdrun(), 187 | NodeName = [16#1f608], 188 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []), 189 | {ok,NodeInfo} = port_please_v2(NodeName), 190 | NodeName = NodeInfo#node_info.node_name, 191 | ok = close(Sock), 192 | ok. 193 | 194 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 195 | 196 | %% Check that we can register and lookup a long unicode name 197 | long_unicode_name(Config) when is_list(Config) -> 198 | ok = epmdrun(), 199 | BaseChar = 16#1f600, 200 | NodeName = lists:seq(BaseChar, BaseChar+200), % will be 800 bytes long 201 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []), 202 | {ok,NodeInfo} = port_please_v2(NodeName), 203 | NodeName = NodeInfo#node_info.node_name, 204 | ok = close(Sock), 205 | ok. 206 | 207 | % Internal function to register a node name, no close, i.e. unregister 208 | register_node6(Name) -> 209 | register_node_v2({0,0,0,0,0,0,0,1},?DUMMY_PORT,$M,0,5,5,Name,""). 210 | 211 | register_node(Name) -> 212 | register_node_v2("localhost",?DUMMY_PORT,$M,0,5,5,Name,""). 213 | 214 | register_node(Name,Port) -> 215 | register_node_v2(Port,$M,0,5,5,Name,""). 216 | 217 | register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 218 | register_node_v2("localhost", Port, NodeType, Prot, HVsn, LVsn, Name, Extra). 219 | register_node_v2(Addr, Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 220 | Req = alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra), 221 | case send_req(Req, Addr) of 222 | {ok,Sock} -> 223 | case recv(Sock,4) of 224 | {ok, [?EPMD_ALIVE2_RESP,_Res=0,_C0,_C1]} -> 225 | {ok,Sock}; 226 | Other -> 227 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 228 | error 229 | end; 230 | error -> 231 | error 232 | end. 233 | 234 | %% Register a name over IPv6 235 | register_name_ipv6(Config) when is_list(Config) -> 236 | % Test if the host has an IPv6 loopback address 237 | Res = gen_tcp:listen(0, [inet6, {ip, {0,0,0,0,0,0,0,1}}]), 238 | case Res of 239 | {ok,LSock} -> 240 | gen_tcp:close(LSock), 241 | ok = epmdrun(), 242 | {ok,Sock} = register_node6("foobar6"), 243 | ok = close(Sock), % Unregister 244 | ok; 245 | _Error -> 246 | {skip, "Host does not have an IPv6 loopback address"} 247 | end. 248 | 249 | % Internal function to fetch information about a node 250 | port_please_v2(Name) -> 251 | case send_req([?EPMD_PORT_PLEASE2_REQ, 252 | binary_to_list(unicode:characters_to_binary(Name))]) of 253 | {ok,Sock} -> 254 | case recv_until_sock_closes(Sock) of 255 | {ok, Resp} -> 256 | parse_port2_resp(Resp); 257 | Other -> 258 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 259 | error 260 | end; 261 | error -> 262 | error 263 | end. 264 | 265 | parse_port2_resp(Resp) -> 266 | case list_to_binary(Resp) of 267 | <> when Res =:= 0 -> 270 | {ok, #node_info{port=Port,node_type=NodeType,prot=Prot, 271 | hvsn=HVsn,lvsn=LVsn, 272 | node_name=unicode:characters_to_list(NodeName), 273 | extra=binary_to_list(Extra)}}; 274 | _Other -> 275 | io:format("invalid port2 resp: ~p~n", [Resp]), 276 | error 277 | end. 278 | 279 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 280 | 281 | %% Register a name with a null char in it 282 | name_with_null_inside(Config) when is_list(Config) -> 283 | ok = epmdrun(), 284 | error = register_node("foo\000bar"), 285 | ok. 286 | 287 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 288 | 289 | %% Register a name with terminating null byte 290 | name_null_terminated(Config) when is_list(Config) -> 291 | ok = epmdrun(), 292 | error = register_node("foobar\000"), 293 | ok. 294 | 295 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 296 | 297 | %% Read names from epmd in a stupid way 298 | stupid_names_req(Config) when is_list(Config) -> 299 | ct:timetrap({minutes, 3}), 300 | ok = epmdrun(), 301 | [FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"), 302 | unregister_many([FirstConn]), 303 | sleep(?MEDIUM_PAUSE), 304 | ok = check_names(Conn), 305 | ok = unregister_many(Conn), 306 | ok. 307 | 308 | check_names(Conn) -> 309 | {ok,Sock} = connect_active(), 310 | {ok,Reply} = do_get_names(Sock), 311 | SortConn = lists:sort(Conn), 312 | SortReply = lists:sort(Reply), 313 | ok = check_names_cmp(SortConn, SortReply), 314 | ok. 315 | 316 | 317 | % Compare if the result was the same as was registered 318 | 319 | check_names_cmp([], []) -> 320 | ok; 321 | check_names_cmp([{Name,Port,_Sock} | Conn], [{Name,Port} | Reply]) -> 322 | check_names_cmp(Conn, Reply). 323 | 324 | 325 | % This code is taken directly from "erl_epmd.erl" in R3A01 326 | 327 | -define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]). 328 | -define(u32(X1,X2,X3,X4), 329 | (((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)). 330 | 331 | do_get_names(Socket) -> 332 | inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES_REQ]), 333 | receive 334 | {tcp, Socket, [P0,P1,P2,P3 | T]} -> 335 | EpmdPort = ?u32(P0,P1,P2,P3), 336 | if EpmdPort == ?PORT -> 337 | names_loop(Socket, T, []); 338 | true -> 339 | close(Socket), 340 | {error, address} 341 | end; 342 | {tcp_closed, Socket} -> 343 | {ok, []} 344 | end. 345 | 346 | names_loop(Socket, Acc, Ps) -> 347 | receive 348 | {tcp, Socket, Bytes} -> 349 | {NAcc, NPs} = scan_names(Acc ++ Bytes, Ps), 350 | names_loop(Socket, NAcc, NPs); 351 | {tcp_closed, Socket} -> 352 | {_, NPs} = scan_names(Acc, Ps), % Really needed? 353 | {ok, NPs} 354 | end. 355 | 356 | scan_names(Buf, Ps) -> 357 | case scan_line(Buf, []) of 358 | {Line, NBuf} -> 359 | case parse_line(Line) of 360 | {ok, Entry} -> 361 | scan_names(NBuf, [Entry | Ps]); 362 | error -> 363 | scan_names(NBuf, Ps) 364 | end; 365 | [] -> {Buf, Ps} 366 | end. 367 | 368 | scan_line([$\n | Buf], Line) -> {lists:reverse(Line), Buf}; 369 | scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]); 370 | scan_line([], _) -> []. 371 | 372 | parse_line([$n,$a,$m,$e,$ | Buf0]) -> 373 | case parse_name(Buf0, []) of 374 | {Name, Buf1} -> 375 | case Buf1 of 376 | [$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] -> 377 | case catch list_to_integer(Buf2) of 378 | {'EXIT', _} -> error; 379 | Port -> {ok, {Name, Port}} 380 | end; 381 | _ -> error 382 | end; 383 | error -> error 384 | end; 385 | parse_line(_) -> error. 386 | 387 | 388 | parse_name([$ | Buf], Name) -> {lists:reverse(Name), Buf}; 389 | parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]); 390 | parse_name([], _Name) -> error. 391 | 392 | 393 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 394 | 395 | %% Register a name on a port and ask about port nr 396 | get_port_nr(Config) when is_list(Config) -> 397 | port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]). 398 | 399 | %% Register with slow write and ask about port nr 400 | slow_get_port_nr(Config) when is_list(Config) -> 401 | port_request([?EPMD_PORT_PLEASE2_REQ,d,$f,d,$o,d,$o]). 402 | 403 | 404 | % Internal function used above 405 | 406 | port_request(M) -> 407 | ok = epmdrun(), 408 | Port = 1042, 409 | {ok,RSock} = register_node("foo", Port), 410 | {ok,Sock} = connect(), 411 | ok = send(Sock,[size16(M),M]), 412 | case recv_until_sock_closes(Sock) of 413 | {ok, Resp} -> 414 | close(RSock), 415 | {ok,Rec} = parse_port2_resp(Resp), 416 | Port = Rec#node_info.port, 417 | ok; 418 | Other -> 419 | close(RSock), 420 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 421 | throw({error,Other}) 422 | end, 423 | ok. 424 | 425 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 426 | 427 | %% Unregister name of other node 428 | unregister_others_name_1(Config) when is_list(Config) -> 429 | ok = epmdrun("-relaxed_command_check"), 430 | {ok,RSock} = register_node("foo"), 431 | {ok,Sock} = connect(), 432 | M = [?EPMD_STOP_REQ,"foo"], 433 | ok = send(Sock,[size16(M),M]), 434 | R = "STOPPED", 435 | {ok,R} = recv(Sock,length(R)), 436 | ok = close(RSock), 437 | ok. 438 | 439 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 440 | 441 | %% Unregister name of other node 442 | unregister_others_name_2(Config) when is_list(Config) -> 443 | ok = epmdrun("-relaxed_command_check"), 444 | {ok,Sock} = connect(), 445 | M = [?EPMD_STOP_REQ,"xxx42"], 446 | ok = send(Sock,[size16(M),M]), 447 | R = "NOEXIST", 448 | {ok,R} = recv(Sock,length(R)), 449 | ok. 450 | 451 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 452 | 453 | %% Register too many, clean and redo 10 times 454 | register_overflow(Config) when is_list(Config) -> 455 | ct:timetrap({minutes, 10}), 456 | ok = epmdrun(), 457 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 458 | Count = length(Conn), 459 | ok = unregister_many(Conn), 460 | sleep(?MEDIUM_PAUSE), 461 | io:format("Limit was ~w names, now reg/unreg all 10 times~n", [Count]), 462 | ok = register_repeat(Count), 463 | sleep(?MEDIUM_PAUSE), 464 | ok = rregister_repeat(Count), 465 | sleep(?MEDIUM_PAUSE), 466 | ok = register_repeat(Count), 467 | sleep(?MEDIUM_PAUSE), 468 | ok = rregister_repeat(Count), 469 | sleep(?MEDIUM_PAUSE), 470 | ok = register_repeat(Count), 471 | sleep(?MEDIUM_PAUSE), 472 | ok = rregister_repeat(Count), 473 | sleep(?MEDIUM_PAUSE), 474 | ok = register_repeat(Count), 475 | sleep(?MEDIUM_PAUSE), 476 | ok = rregister_repeat(Count), 477 | sleep(?MEDIUM_PAUSE), 478 | ok = register_repeat(Count), 479 | sleep(?MEDIUM_PAUSE), 480 | ok = rregister_repeat(Count), 481 | ok. 482 | 483 | register_repeat(Count) -> 484 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 485 | ok = unregister_many(Conn), 486 | if 487 | length(Conn) == Count -> 488 | ok; 489 | true -> 490 | error 491 | end. 492 | 493 | rregister_repeat(Count) -> 494 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 495 | ok = unregister_many(lists:reverse(Conn)), 496 | if 497 | length(Conn) == Count -> 498 | ok; 499 | true -> 500 | error 501 | end. 502 | 503 | % Return count of successful registrations 504 | 505 | register_many(I, N, _Prefix) when I > N -> 506 | io:format("Done with all ~n", []), []; 507 | register_many(I, N, Prefix) -> 508 | Name = gen_name(Prefix, I), 509 | Port = ?DUMMY_PORT + I, % Just make it up 510 | case register_node(Name, Port) of 511 | {ok,Sock} -> 512 | [{Name,Port,Sock} | register_many(I + 1, N, Prefix)]; 513 | Any -> 514 | io:format("Can't register: ~w of 1..~w ~w~n", [Name,N,Any]), 515 | [] 516 | end. 517 | 518 | unregister_many([]) -> 519 | ok; 520 | unregister_many([{Name,_Port,Sock} | Socks]) -> 521 | case close(Sock) of 522 | ok -> 523 | unregister_many(Socks); 524 | Any -> 525 | io:format("Can't unregister: ~w reason ~w~n", [Name,Any]), 526 | error 527 | end. 528 | 529 | gen_name(Str,Int) -> 530 | Str ++ integer_to_list(Int). 531 | 532 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 533 | 534 | %% Open but send no data 535 | no_data(Config) when is_list(Config) -> 536 | {skip, "Unsure"}. 537 | % ok = epmdrun(), 538 | % {ok,Sock} = connect(), 539 | % sleep(?LONG_PAUSE), 540 | % closed = recv(Sock,1), 541 | % ok. 542 | 543 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 544 | 545 | %% Send one byte only 546 | one_byte(Config) when is_list(Config) -> 547 | {skip, "Unsure"}. 548 | % ok = epmdrun(), 549 | % {ok,Sock} = connect(), 550 | % ok = send(Sock,[0]), 551 | % sleep(?LONG_PAUSE), 552 | % closed = recv(Sock,1), 553 | % ok. 554 | 555 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 556 | 557 | %% Send packet size only 558 | two_bytes(Config) when is_list(Config) -> 559 | {skip, "Unsure"}. 560 | % ok = epmdrun(), 561 | % {ok,Sock} = connect(), 562 | % ok = send(Sock,[put16(3)]), 563 | % sleep(?LONG_PAUSE), 564 | % closed = recv(Sock,1), 565 | % ok. 566 | 567 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 568 | 569 | %% Got only part of a packet 570 | partial_packet(Config) when is_list(Config) -> 571 | {skip, "Unsure"}. 572 | % ok = epmdrun(), 573 | % {ok,Sock} = connect(), 574 | % ok = send(Sock,[put16(100),"only a few bytes"]), 575 | % sleep(?LONG_PAUSE), 576 | % closed = recv(Sock,1), 577 | % ok. 578 | 579 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 580 | 581 | %% Invalid zero packet size 582 | zero_length(Config) when is_list(Config) -> 583 | ok = epmdrun(), 584 | {ok,Sock} = connect(), 585 | ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), 586 | sleep(?MEDIUM_PAUSE), 587 | closed = recv(Sock,1), 588 | ok. 589 | 590 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 591 | 592 | %% Invalid large packet 593 | too_large(Config) when is_list(Config) -> 594 | ok = epmdrun(), 595 | {ok,Sock} = connect(), 596 | Size = 63000, 597 | M = lists:duplicate(Size, $z), 598 | ok = send(Sock,[put16(Size),M]), 599 | sleep(?MEDIUM_PAUSE), 600 | % With such a large packet, even the writes can fail as the 601 | % daemon closes before everything is delivered -> econnaborted 602 | case recv(Sock,1) of 603 | closed -> ok; 604 | {error,econnaborted} -> ok; 605 | Other -> exit({unexpected,Other}) 606 | end. 607 | 608 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 609 | 610 | %% Try to register but not enough data 611 | alive_req_too_small_1(Config) when is_list(Config) -> 612 | ok = epmdrun(), 613 | {ok,Sock} = connect(), 614 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), 615 | put16(5),put16(0)], 616 | ok = send(Sock, [size16(M), M]), 617 | sleep(?MEDIUM_PAUSE), 618 | closed = recv(Sock,1), 619 | ok. 620 | 621 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 622 | 623 | %% Try to register but not enough data 624 | alive_req_too_small_2(Config) when is_list(Config) -> 625 | ok = epmdrun(), 626 | {ok,Sock} = connect(), 627 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), put16(5)], 628 | ok = send(Sock, [size16(M), M]), 629 | sleep(?MEDIUM_PAUSE), 630 | closed = recv(Sock,1), 631 | ok. 632 | 633 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 634 | 635 | %%Try to register but node name too large 636 | alive_req_too_large(Config) when is_list(Config) -> 637 | ok = epmdrun(), 638 | {ok,Sock} = connect(), 639 | L = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 640 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 641 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 642 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 643 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 644 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 645 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 646 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 647 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 648 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 649 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 650 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], 651 | S = length(lists:flatten(L)), 652 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), 653 | put16(5), put16(S),L,put16(0)], 654 | ok = send(Sock, [size16(M), M]), 655 | sleep(?MEDIUM_PAUSE), 656 | Wat = recv(Sock,2), 657 | io:format("too_large ~p~n", [Wat]), 658 | {ok,[?EPMD_ALIVE2_RESP,1]} = Wat, 659 | ok. 660 | 661 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 662 | 663 | %% Check that an empty extra is prefixed by a two byte length 664 | returns_valid_empty_extra(Config) when is_list(Config) -> 665 | ok = epmdrun(), 666 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []), 667 | {ok,#node_info{extra=[]}} = port_please_v2("foo"), 668 | ok = close(Sock), 669 | ok. 670 | 671 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 672 | 673 | %% Check a populated extra with embedded null characters 674 | returns_valid_populated_extra_with_nulls(Config) when is_list(Config) -> 675 | ok = epmdrun(), 676 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"), 677 | {ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"), 678 | ok = close(Sock), 679 | ok. 680 | 681 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 682 | 683 | %% Test that epmd -names prints registered nodes to stdout 684 | names_stdout(Config) when is_list(Config) -> 685 | ok = epmdrun(), 686 | {ok,Sock} = register_node("foobar"), 687 | Data = os:cmd(?EPMD ++ " " ++ epmd_port_arg() ++ " -names"), 688 | io:format("NAMES: ~p~n", [Data]), 689 | {match,_} = re:run(Data, "^epmd: up and running", [multiline]), 690 | {match,_} = re:run(Data, "^name foobar at port", [multiline]), 691 | ok = close(Sock), 692 | ok. 693 | 694 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 695 | 696 | %% Test security vulnerability in fake extra lengths in alive2_req 697 | buffer_overrun_1(Config) when is_list(Config) -> 698 | ok = epmdrun(), 699 | true = alltrue([hostile(N) || N <- lists:seq(1,10000)]), 700 | ok. 701 | 702 | %% Test security vulnerability in fake extra lengths in alive2_req 703 | buffer_overrun_2(Config) when is_list(Config) -> 704 | ok = epmdrun(), 705 | [false | Rest] = [hostile2(N) || N <- lists:seq(255*4,10000)], 706 | true = alltrue(Rest), 707 | ok. 708 | hostile(N) -> 709 | try 710 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16>>, 711 | S = size(Bin), 712 | {ok,E}=connect_sturdy(), 713 | gen_tcp:send(E,[<>,Bin]), 714 | closed = recv(E,1), 715 | gen_tcp:close(E), 716 | true 717 | catch 718 | _:_ -> 719 | false 720 | end. 721 | hostile2(N) -> 722 | try 723 | B2 = list_to_binary(lists:duplicate(N,255)), 724 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16,B2/binary>>, 725 | S = size(Bin), 726 | {ok,E}=connect_sturdy(), 727 | gen_tcp:send(E,[<>,Bin]), 728 | Z = recv(E,2), 729 | gen_tcp:close(E), 730 | (Z =:= closed) or (Z =:= {ok, [$y,1]}) 731 | catch 732 | _A:_B -> 733 | false 734 | end. 735 | 736 | alltrue([]) -> 737 | true; 738 | alltrue([true|T]) -> 739 | alltrue(T); 740 | alltrue([_|_]) -> 741 | false. 742 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 743 | 744 | %% Ensure that we cannot register throug a nonlocal connection 745 | no_nonlocal_register(Config) when is_list(Config) -> 746 | case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of 747 | {SSH,Name} when is_list(Name), is_list(SSH) -> 748 | do_no_nonlocal_register(Config,Name); 749 | {false,_} -> 750 | {skip, "No ssh command found to create proxy"}; 751 | _ -> 752 | {skip, "No ssh_proxy_host configured in ts.config"} 753 | end. 754 | do_no_nonlocal_register(Config,SSHHost) when is_list(Config) -> 755 | ok = epmdrun(), 756 | ProxyPort = proxy_port(), 757 | ok = ssh_proxy(SSHHost,ProxyPort), 758 | Res = try 759 | Name = "gurka_" 760 | %++ 761 | %integer_to_list(A1)++"_"++ 762 | %integer_to_list(A2)++"_"++ 763 | %integer_to_list(A3)++"_"++ 764 | %integer_to_list(A4) 765 | , 766 | Bname = list_to_binary(Name), 767 | NameS = byte_size(Bname), 768 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16, 769 | 5:16,NameS:16,Bname/binary, 770 | 0:16>>, 771 | S = size(Bin), 772 | {ok, E} = connect("localhost",ProxyPort,passive), 773 | gen_tcp:send(E,[<>,Bin]), 774 | closed = recv(E,1), 775 | gen_tcp:close(E), 776 | true 777 | catch 778 | _:_ -> 779 | false 780 | end, 781 | %erlang:display(Res), 782 | true = Res, 783 | ok. 784 | 785 | %% Ensure that we cannot kill through nonlocal connection 786 | no_nonlocal_kill(Config) when is_list(Config) -> 787 | case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of 788 | {SSH,Name} when is_list(Name), is_list(SSH) -> 789 | do_no_nonlocal_kill(Config,Name); 790 | {false,_} -> 791 | {skip, "No ssh command found to create proxy"}; 792 | _ -> 793 | {skip, "No ssh_proxy_host configured in ts.config"} 794 | end. 795 | do_no_nonlocal_kill(Config,SSHHost) when is_list(Config) -> 796 | ok = epmdrun(), 797 | ProxyPort = proxy_port(), 798 | ok = ssh_proxy(SSHHost,ProxyPort), 799 | Res = try 800 | {ok, E} = connect("localhost",ProxyPort,passive), 801 | M = [?EPMD_KILL_REQ], 802 | send(E, [size16(M), M]), 803 | closed = recv(E,2), 804 | gen_tcp:close(E), 805 | sleep(?MEDIUM_PAUSE), 806 | {ok, E2} = connect("localhost",ProxyPort,passive), 807 | gen_tcp:close(E2), 808 | true 809 | catch 810 | _:_ -> 811 | false 812 | end, 813 | %erlang:display(Res), 814 | true = Res, 815 | ok. 816 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 817 | 818 | %% Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check 819 | no_live_killing(Config) when is_list(Config) -> 820 | ok = epmdrun(), 821 | {ok,RSock} = register_node("foo"), 822 | {ok,Sock} = connect(), 823 | M = [?EPMD_KILL_REQ], 824 | ok = send(Sock,[size16(M),M]), 825 | {ok,"NO"} = recv(Sock,2), 826 | close(Sock), 827 | {ok,Sock2} = connect(), 828 | M2 = [?EPMD_STOP_REQ,"foo"], 829 | ok = send(Sock2,[size16(M2),M2]), 830 | closed = recv(Sock2,1), 831 | close(Sock2), 832 | close(RSock), 833 | sleep(?MEDIUM_PAUSE), 834 | {ok,Sock3} = connect(), 835 | M3 = [?EPMD_KILL_REQ], 836 | ok = send(Sock3,[size16(M3),M3]), 837 | {ok,"OK"} = recv(Sock3,2), 838 | close(Sock3), 839 | ok. 840 | 841 | %% Check for regression - don't make zombie from node which 842 | %% sends TCP RST at wrong time 843 | socket_reset_before_alive2_reply_is_written(Config) when is_list(Config) -> 844 | %% - delay_write for easier triggering of race condition 845 | %% - relaxed_command_check for gracefull shutdown of epmd even if there 846 | %% is stuck node. 847 | ok = epmdrun("-delay_write 1 -relaxed_command_check"), 848 | 849 | %% We can't use send_req/1 directly as we want to do inet:setopts/2 850 | %% on our socket. 851 | {ok, Sock} = connect(), 852 | 853 | %% Issuing close/1 on such socket will result in immediate RST packet. 854 | ok = inet:setopts(Sock, [{linger, {true, 0}}]), 855 | 856 | Req = alive2_req(4711, 77, 0, 5, 5, "test", []), 857 | ok = send(Sock, [size16(Req), Req]), 858 | 859 | timer:sleep(500), %% Wait for the first 1/2 of delay_write before closing 860 | ok = close(Sock), 861 | 862 | timer:sleep(500 + ?SHORT_PAUSE), %% Wait for the other 1/2 of delay_write 863 | 864 | %% Wait another delay_write interval, due to delay doubling in epmd. 865 | %% Should be removed when this is issue is fixed there. 866 | timer:sleep(5000), 867 | 868 | {ok, SockForNames} = connect_active(), 869 | io:format("sockfornames ~p~n", [SockForNames]), 870 | 871 | %% And there should be no stuck nodes 872 | {ok, []} = do_get_names(SockForNames), 873 | ok = close(SockForNames), 874 | ok. 875 | 876 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 877 | % Terminate all tests with killing epmd. 878 | 879 | cleanup() -> 880 | %% sleep(?MEDIUM_PAUSE), 881 | case connect("localhost",?PORT, passive, ?CONN_SLEEP, 2) of 882 | {ok,Sock} -> 883 | M = [?EPMD_KILL_REQ], 884 | send(Sock, [size16(M), M]), 885 | recv(Sock,length("OK")), 886 | close(Sock), 887 | sleep(?MEDIUM_PAUSE); 888 | _ -> 889 | true 890 | end. 891 | 892 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 893 | % Start an ssh channel to simulate remote access 894 | 895 | proxy_port() -> 896 | ?PORT+1. 897 | 898 | ssh_proxy(SSHHost,ProxyPort) -> 899 | Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")), 900 | % Requires proxy to be a unix host with the command 'read' accessible 901 | osrun("ssh -L "++integer_to_list(ProxyPort)++":"++Host++":" 902 | ++integer_to_list(?PORT)++" "++SSHHost++" read"). 903 | 904 | 905 | 906 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 907 | % Normal debug start of epmd 908 | 909 | epmdrun() -> 910 | epmdrun([]). 911 | epmdrun(Args) -> 912 | epmdrun(?EPMD, Args). 913 | 914 | epmdrun(Epmd,Args0) -> 915 | Args = case Args0 of 916 | [] -> []; 917 | O -> " " ++ O 918 | end, 919 | osrun(Epmd 920 | ++ " " ?EPMDARGS 921 | ++ " " ++ epmd_port_arg() 922 | ++ Args). 923 | 924 | epmd_port_arg() -> 925 | "-port " ++ integer_to_list(?PORT). 926 | 927 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 928 | % Start an external process 929 | 930 | osrun(Cmd) -> 931 | io:format("osrun(~p)~n",[Cmd]), 932 | spawn_link(fun() -> flusher(Cmd) end), 933 | ok. 934 | 935 | flusher(Cmd) -> 936 | Port = open_port({spawn, Cmd}, [stderr_to_stdout]), 937 | link(Port), 938 | flusher(). 939 | 940 | flusher() -> 941 | receive 942 | {_,{data,Msg}} -> 943 | io:format("from epmd: ~s~n", [Msg]), 944 | flusher(); 945 | Msg -> 946 | io:format("from epmd (unexpected): ~p~n", [Msg]), 947 | flusher() 948 | end. 949 | 950 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 951 | % Wrappers of TCP functions 952 | 953 | % These functions is the interface for connect. 954 | % Passive mode is the default 955 | 956 | connect() -> 957 | connect("localhost",?PORT, passive). 958 | 959 | connect(Addr) -> 960 | connect(Addr,?PORT, passive). 961 | 962 | connect_active() -> 963 | connect("localhost",?PORT, active). 964 | 965 | %% Retry after 2 seconds, to avoid TIME_WAIT socket exhaust. 966 | connect_sturdy() -> 967 | connect("localhost",?PORT, passive, 2000, 3). 968 | 969 | % Try a few times before giving up 970 | connect(Addr, Port, Mode) -> 971 | connect(Addr, Port, Mode, ?CONN_SLEEP, ?CONN_RETRY). 972 | connect(Addr, Port, Mode, Sleep, Retry) -> 973 | case connect_repeat(Addr, Retry, Port, Mode, Sleep) of 974 | {ok,Sock} -> 975 | {ok,Sock}; 976 | {error,timeout} -> 977 | timeout; 978 | {error,Reason} -> 979 | io:format("connect: error: ~w~n",[Reason]), 980 | error; 981 | Any -> 982 | io:format("connect: unknown message: ~w~n",[Any]), 983 | exit(1) 984 | end. 985 | 986 | 987 | % Try a few times before giving up. Pause a small time between 988 | % each try. 989 | 990 | connect_repeat(Addr, 1, Port, Mode, _Sleep) -> 991 | connect_mode(Addr,Port, Mode); 992 | connect_repeat(Addr,Retry, Port, Mode, Sleep) -> 993 | case connect_mode(Addr,Port, Mode) of 994 | {ok,Sock} -> 995 | {ok,Sock}; 996 | {error,Reason} -> 997 | io:format("connect: error: ~w sleeping for ~w~n",[Reason,Sleep]), 998 | timer:sleep(Sleep), 999 | connect_repeat(Addr, Retry - 1, Port, Mode, 2*Sleep); 1000 | Any -> 1001 | io:format("connect: unknown message: ~w~n",[Any]), 1002 | exit(1) 1003 | end. 1004 | 1005 | connect_mode(Addr,Port, active) -> 1006 | gen_tcp:connect(Addr, Port, [{packet, 0}], ?CONN_TIMEOUT); 1007 | connect_mode(Addr, Port, passive) -> 1008 | gen_tcp:connect(Addr, Port, [{packet, 0}, {active, false}], 1009 | ?CONN_TIMEOUT). 1010 | 1011 | 1012 | close(Sock) -> 1013 | case gen_tcp:close(Sock) of 1014 | {error,_} -> 1015 | error; 1016 | ok -> 1017 | ok; 1018 | Any -> 1019 | io:format("unknown message: ~w~n",[Any]), 1020 | exit(1) 1021 | end. 1022 | 1023 | recv(Sock, Len) -> 1024 | recv(Sock, Len, ?RECV_TIMEOUT). 1025 | 1026 | recv(Sock, Len, Timeout) -> 1027 | case gen_tcp:recv(Sock, Len, Timeout) of 1028 | {ok,[]} -> % Should not be the case 1029 | recv(Sock, 1, 1); % any longer 1030 | {ok,Data} -> 1031 | {ok,Data}; 1032 | {error,timeout} -> 1033 | timeout; 1034 | {error,closed} -> 1035 | closed; 1036 | {error,_}=Error -> 1037 | Error; 1038 | Any -> 1039 | io:format("unknown message: ~w~n",[Any]), 1040 | exit(1) 1041 | end. 1042 | 1043 | %% Send data to socket. The list can be non flat and contain 1044 | %% the atom 'd' or tuple {d,Seconds} where this is delay 1045 | %% put in between the sent characters. 1046 | 1047 | send(Sock, SendSpec) -> 1048 | case send(SendSpec, [], Sock) of 1049 | {ok,[]} -> 1050 | ok; 1051 | {ok,RevBytes} -> 1052 | send_direct(Sock, lists:reverse(RevBytes)); 1053 | Any -> 1054 | Any 1055 | end. 1056 | 1057 | 1058 | % If an error, return immediately 1059 | % Collect real characters in the first argument to form 1060 | % a string to send. Only perform "actions", like a delay, 1061 | % when this argument is empty. 1062 | 1063 | send([], RevBytes, _Sock) -> 1064 | {ok,RevBytes}; 1065 | send([Byte | Spec], RevBytes, Sock) when is_integer(Byte) -> 1066 | send(Spec, [Byte | RevBytes], Sock); 1067 | send([List | Spec], RevBytes, Sock) when is_list(List) -> 1068 | case send(List, RevBytes, Sock) of 1069 | {ok,Left} -> 1070 | send(Spec, Left, Sock); 1071 | Other -> 1072 | Other 1073 | end; 1074 | send([d | Spec], RevBytes, Sock) -> 1075 | send([{d,1000} | Spec], RevBytes, Sock); 1076 | send([{d,S} | Spec], RevBytes, Sock) -> 1077 | case send_direct(Sock, lists:reverse(RevBytes)) of 1078 | ok -> 1079 | timer:sleep(S), 1080 | send(Spec, [], Sock); 1081 | Any -> 1082 | Any 1083 | end. 1084 | 1085 | %%%% 1086 | 1087 | send_direct(Sock, Bytes) -> 1088 | case gen_tcp:send(Sock, Bytes) of 1089 | ok -> 1090 | ok; 1091 | {error, closed} -> 1092 | closed; 1093 | {error, _Reason} -> 1094 | error; 1095 | Any -> 1096 | io:format("unknown message: ~w~n",[Any]), 1097 | Any 1098 | end. 1099 | 1100 | send_req(Req) -> 1101 | send_req(Req, "localhost"). 1102 | send_req(Req,Addr) -> 1103 | case connect(Addr) of 1104 | {ok,Sock} -> 1105 | case send(Sock, [size16(Req), Req]) of 1106 | ok -> 1107 | {ok,Sock}; 1108 | Other -> 1109 | io:format("Failed to send ~w on sock ~w: ~w~n", 1110 | [Req,Sock,Other]), 1111 | error 1112 | end; 1113 | Other -> 1114 | io:format("Connect failed when sending ~w: ~p~n", 1115 | [Req, Other]), 1116 | error 1117 | end. 1118 | 1119 | recv_until_sock_closes(Sock) -> 1120 | recv_until_sock_closes_2(Sock,[]). 1121 | 1122 | recv_until_sock_closes_2(Sock,AccData) -> 1123 | case recv(Sock,0) of 1124 | {ok,Data} -> 1125 | recv_until_sock_closes_2(Sock,AccData++Data); 1126 | closed -> 1127 | {ok,AccData}; 1128 | Other -> 1129 | Other 1130 | end. 1131 | 1132 | sleep(MilliSeconds) -> 1133 | timer:sleep(MilliSeconds). 1134 | 1135 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1136 | 1137 | put16(N) -> 1138 | [N bsr 8, N band 16#ff]. 1139 | 1140 | size16(List) -> 1141 | N = flat_count(List, 0), 1142 | [N bsr 8, N band 16#ff]. 1143 | 1144 | flat_count([H|T], N) when is_integer(H) -> 1145 | flat_count(T, N+1); 1146 | flat_count([H|T], N) when is_list(H) -> 1147 | flat_count(T, flat_count(H, N)); 1148 | flat_count([_|T], N) -> 1149 | flat_count(T, N); 1150 | flat_count([], N) -> N. 1151 | 1152 | 1153 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1154 | 1155 | alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 1156 | Utf8Name = unicode:characters_to_binary(Name), 1157 | [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot, 1158 | put16(HVsn), put16(LVsn), 1159 | put16(size(Utf8Name)), binary_to_list(Utf8Name), 1160 | size16(Extra), Extra]. 1161 | -------------------------------------------------------------------------------- /test/epmd_erlang_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1998-2013. 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(epmd_erlang_SUITE). 21 | 22 | -include_lib("common_test/include/ct.hrl"). 23 | -include_lib("kernel/include/file.hrl"). 24 | 25 | % Timeout for test cases (rather long to work on slow machines) 26 | 27 | % Delay inserted into code [ms] 28 | -define(SHORT_PAUSE, 100). 29 | -define(MEDIUM_PAUSE, 300). 30 | -define(LONG_PAUSE, 500). 31 | -define(EPMD, "../../bin/epmd"). 32 | 33 | % Information about nodes 34 | -record(node_info, {port, node_type, prot, lvsn, hvsn, node_name, extra}). 35 | 36 | % Test server specific exports 37 | -export([all/0, suite/0, groups/0, 38 | init_per_testcase/2, end_per_testcase/2]). 39 | 40 | -export([register_name/1, 41 | register_name_ipv6/1, 42 | register_names_1/1, 43 | register_names_2/1, 44 | register_duplicate_name/1, 45 | unicode_name/1, 46 | long_unicode_name/1, 47 | get_port_nr/1, 48 | slow_get_port_nr/1, 49 | unregister_others_name_1/1, 50 | unregister_others_name_2/1, 51 | register_overflow/1, 52 | name_with_null_inside/1, 53 | name_null_terminated/1, 54 | stupid_names_req/1, 55 | 56 | no_data/1, 57 | one_byte/1, 58 | two_bytes/1, 59 | partial_packet/1, 60 | zero_length/1, 61 | too_large/1, 62 | alive_req_too_small_1/1, 63 | alive_req_too_small_2/1, 64 | alive_req_too_large/1, 65 | 66 | returns_valid_empty_extra/1, 67 | returns_valid_populated_extra_with_nulls/1, 68 | 69 | names_stdout/1, 70 | 71 | buffer_overrun_1/1, 72 | buffer_overrun_2/1, 73 | no_nonlocal_register/1, 74 | no_nonlocal_kill/1, 75 | no_live_killing/1, 76 | 77 | socket_reset_before_alive2_reply_is_written/1]). 78 | 79 | 80 | % Port we use for testing 81 | -define(PORT,2244). 82 | 83 | -define(DUMMY_PORT, 1000). % Port number to register 84 | % not in real use. 85 | 86 | % Timeouts etc inside test cases. Time is in milliseconds. 87 | -define(CONN_RETRY, 4). % Times to retry connecting 88 | -define(CONN_SLEEP, 50). % Inital, doubles every retry 89 | -define(CONN_TIMEOUT, 100). 90 | -define(RECV_TIMEOUT, 2000). 91 | -define(REG_REPEAT_LIM,1000). 92 | 93 | % Message codes in epmd protocol 94 | -define(EPMD_ALIVE2_REQ, $x). 95 | -define(EPMD_ALIVE2_RESP, $y). 96 | -define(EPMD_PORT_PLEASE2_REQ, $z). 97 | -define(EPMD_PORT2_RESP, $w). 98 | -define(EPMD_NAMES_REQ, $n). 99 | -define(EPMD_DUMP_REQ, $d). 100 | -define(EPMD_KILL_REQ, $k). 101 | -define(EPMD_STOP_REQ, $s). 102 | 103 | %% 104 | %% all/1 105 | %% 106 | 107 | suite() -> 108 | [{timetrap, {minutes, 3}}]. 109 | 110 | all() -> 111 | [register_name, register_name_ipv6, 112 | register_names_1, register_names_2, 113 | register_duplicate_name, unicode_name, long_unicode_name, 114 | get_port_nr, slow_get_port_nr, 115 | unregister_others_name_1, unregister_others_name_2, 116 | register_overflow, 117 | name_with_null_inside, 118 | name_null_terminated, 119 | stupid_names_req, 120 | no_data, 121 | one_byte, two_bytes, partial_packet, zero_length, 122 | too_large, alive_req_too_small_1, alive_req_too_small_2, 123 | alive_req_too_large, returns_valid_empty_extra, 124 | returns_valid_populated_extra_with_nulls, 125 | names_stdout, 126 | {group, buffer_overrun}, no_nonlocal_register, 127 | no_nonlocal_kill, 128 | no_live_killing, 129 | socket_reset_before_alive2_reply_is_written]. 130 | 131 | groups() -> 132 | [{buffer_overrun, [], 133 | [buffer_overrun_1, buffer_overrun_2]}]. 134 | 135 | %% 136 | %% Run before and after each test case 137 | %% 138 | init_per_testcase(Test, Config) -> 139 | ok = application:unset_env(epmd, relaxed_command_check), 140 | case Test of 141 | unregister_others_name_1 -> 142 | ok = application:set_env(epmd, relaxed_command_check, true); 143 | unregister_others_name_2 -> 144 | ok = application:set_env(epmd, relaxed_command_check, true); 145 | socket_reset_before_alive2_reply_is_written -> 146 | ok = application:set_env(epmd, relaxed_command_check, true), 147 | ok = application:set_env(epmd, delay_write, 1); 148 | _ -> 149 | ok 150 | end, 151 | ok = application:set_env(epmd, port, ?PORT), 152 | ok = application:start(epmd), 153 | Config. 154 | 155 | end_per_testcase(Test, _Config) -> 156 | case Test of 157 | unregister_others_name_1 -> 158 | ok = application:unset_env(epmd, relaxed_command_check); 159 | unregister_others_name_2 -> 160 | ok = application:unset_env(epmd, relaxed_command_check); 161 | socket_reset_before_alive2_reply_is_written -> 162 | ok = application:unset_env(epmd, relaxed_command_check), 163 | ok = application:unset_env(epmd, delay_write); 164 | _ -> 165 | ok 166 | end, 167 | ok = application:stop(epmd), 168 | ok = application:unset_env(epmd, port), 169 | ok. 170 | 171 | 172 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 173 | 174 | 175 | %% Register a name 176 | register_name(Config) when is_list(Config) -> 177 | {ok,Sock} = register_node("foobar"), 178 | ok = close(Sock), % Unregister 179 | ok. 180 | 181 | %% Register and unregister two nodes 182 | register_names_1(Config) when is_list(Config) -> 183 | {ok,Sock1} = register_node("foobar"), 184 | {ok,Sock2} = register_node("foozap"), 185 | ok = close(Sock1), % Unregister 186 | ok = close(Sock2), % Unregister 187 | ok. 188 | 189 | %% Register and unregister two nodes 190 | register_names_2(Config) when is_list(Config) -> 191 | {ok,Sock1} = register_node("foobar"), 192 | {ok,Sock2} = register_node("foozap"), 193 | ok = close(Sock2), % Unregister 194 | ok = close(Sock1), % Unregister 195 | ok. 196 | 197 | %% Two nodes with the same name 198 | register_duplicate_name(Config) when is_list(Config) -> 199 | {ok,Sock} = register_node("foobar"), 200 | error = register_node("foobar"), 201 | ok = close(Sock), % Unregister 202 | ok. 203 | 204 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 205 | 206 | %% Check that we can register and lookup a unicode name 207 | unicode_name(Config) when is_list(Config) -> 208 | NodeName = [16#1f608], 209 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []), 210 | {ok,NodeInfo} = port_please_v2(NodeName), 211 | NodeName = NodeInfo#node_info.node_name, 212 | ok = close(Sock), 213 | ok. 214 | 215 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 216 | 217 | %% Check that we can register and lookup a long unicode name 218 | long_unicode_name(Config) when is_list(Config) -> 219 | BaseChar = 16#1f600, 220 | NodeName = lists:seq(BaseChar, BaseChar+200), % will be 800 bytes long 221 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, NodeName, []), 222 | {ok,NodeInfo} = port_please_v2(NodeName), 223 | NodeName = NodeInfo#node_info.node_name, 224 | ok = close(Sock), 225 | ok. 226 | 227 | % Internal function to register a node name, no close, i.e. unregister 228 | register_node6(Name) -> 229 | register_node_v2({0,0,0,0,0,0,0,1},?DUMMY_PORT,$M,0,5,5,Name,""). 230 | 231 | register_node(Name) -> 232 | register_node_v2("localhost",?DUMMY_PORT,$M,0,5,5,Name,""). 233 | 234 | register_node(Name,Port) -> 235 | register_node_v2(Port,$M,0,5,5,Name,""). 236 | 237 | register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 238 | register_node_v2("localhost", Port, NodeType, Prot, HVsn, LVsn, Name, Extra). 239 | register_node_v2(Addr, Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 240 | Req = alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra), 241 | case send_req(Req, Addr) of 242 | {ok,Sock} -> 243 | case recv(Sock,4) of 244 | {ok, [?EPMD_ALIVE2_RESP,_Res=0,_C0,_C1]} -> 245 | {ok,Sock}; 246 | Other -> 247 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 248 | error 249 | end; 250 | error -> 251 | error 252 | end. 253 | 254 | %% Register a name over IPv6 255 | register_name_ipv6(Config) when is_list(Config) -> 256 | % Test if the host has an IPv6 loopback address 257 | Res = gen_tcp:listen(0, [inet6, {ip, {0,0,0,0,0,0,0,1}}]), 258 | case Res of 259 | {ok,LSock} -> 260 | gen_tcp:close(LSock), 261 | {ok,Sock} = register_node6("foobar6"), 262 | ok = close(Sock), % Unregister 263 | ok; 264 | _Error -> 265 | {skip, "Host does not have an IPv6 loopback address"} 266 | end. 267 | 268 | % Internal function to fetch information about a node 269 | port_please_v2(Name) -> 270 | case send_req([?EPMD_PORT_PLEASE2_REQ, 271 | binary_to_list(unicode:characters_to_binary(Name))]) of 272 | {ok,Sock} -> 273 | case recv_until_sock_closes(Sock) of 274 | {ok, Resp} -> 275 | parse_port2_resp(Resp); 276 | Other -> 277 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 278 | error 279 | end; 280 | error -> 281 | error 282 | end. 283 | 284 | parse_port2_resp(Resp) -> 285 | case list_to_binary(Resp) of 286 | <> when Res =:= 0 -> 289 | {ok, #node_info{port=Port,node_type=NodeType,prot=Prot, 290 | hvsn=HVsn,lvsn=LVsn, 291 | node_name=unicode:characters_to_list(NodeName), 292 | extra=binary_to_list(Extra)}}; 293 | _Other -> 294 | io:format("invalid port2 resp: ~p~n", [Resp]), 295 | error 296 | end. 297 | 298 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 299 | 300 | %% Register a name with a null char in it 301 | name_with_null_inside(Config) when is_list(Config) -> 302 | error = register_node("foo\000bar"), 303 | ok. 304 | 305 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 306 | 307 | %% Register a name with terminating null byte 308 | name_null_terminated(Config) when is_list(Config) -> 309 | error = register_node("foobar\000"), 310 | ok. 311 | 312 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 313 | 314 | %% Read names from epmd in a stupid way 315 | stupid_names_req(Config) when is_list(Config) -> 316 | ct:timetrap({minutes, 3}), 317 | [FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"), 318 | unregister_many([FirstConn]), 319 | sleep(?MEDIUM_PAUSE), 320 | ok = check_names(Conn), 321 | ok = unregister_many(Conn), 322 | ok. 323 | 324 | check_names(Conn) -> 325 | {ok,Sock} = connect_active(), 326 | {ok,Reply} = do_get_names(Sock), 327 | SortConn = lists:sort(Conn), 328 | SortReply = lists:sort(Reply), 329 | ok = check_names_cmp(SortConn, SortReply), 330 | ok. 331 | 332 | 333 | % Compare if the result was the same as was registered 334 | 335 | check_names_cmp([], []) -> 336 | ok; 337 | check_names_cmp([{Name,Port,_Sock} | Conn], [{Name,Port} | Reply]) -> 338 | check_names_cmp(Conn, Reply). 339 | 340 | 341 | % This code is taken directly from "erl_epmd.erl" in R3A01 342 | 343 | -define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]). 344 | -define(u32(X1,X2,X3,X4), (((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)). 345 | 346 | do_get_names(Socket) -> 347 | inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES_REQ]), 348 | receive 349 | {tcp, Socket, [P0,P1,P2,P3 | T]} -> 350 | EpmdPort = ?u32(P0,P1,P2,P3), 351 | if EpmdPort == ?PORT -> 352 | names_loop(Socket, T, []); 353 | true -> 354 | close(Socket), 355 | {error, address} 356 | end; 357 | {tcp_closed, Socket} -> 358 | {ok, []} 359 | end. 360 | 361 | names_loop(Socket, Acc, Ps) -> 362 | receive 363 | {tcp, Socket, Bytes} -> 364 | {NAcc, NPs} = scan_names(Acc ++ Bytes, Ps), 365 | names_loop(Socket, NAcc, NPs); 366 | {tcp_closed, Socket} -> 367 | {_, NPs} = scan_names(Acc, Ps), % Really needed? 368 | {ok, NPs} 369 | end. 370 | 371 | scan_names(Buf, Ps) -> 372 | case scan_line(Buf, []) of 373 | {Line, NBuf} -> 374 | case parse_line(Line) of 375 | {ok, Entry} -> 376 | scan_names(NBuf, [Entry | Ps]); 377 | error -> 378 | scan_names(NBuf, Ps) 379 | end; 380 | [] -> {Buf, Ps} 381 | end. 382 | 383 | scan_line([$\n | Buf], Line) -> {lists:reverse(Line), Buf}; 384 | scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]); 385 | scan_line([], _) -> []. 386 | 387 | parse_line([$n,$a,$m,$e,$ | Buf0]) -> 388 | case parse_name(Buf0, []) of 389 | {Name, Buf1} -> 390 | case Buf1 of 391 | [$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] -> 392 | case catch list_to_integer(Buf2) of 393 | {'EXIT', _} -> error; 394 | Port -> {ok, {Name, Port}} 395 | end; 396 | _ -> error 397 | end; 398 | error -> error 399 | end; 400 | parse_line(_) -> error. 401 | 402 | 403 | parse_name([$ | Buf], Name) -> {lists:reverse(Name), Buf}; 404 | parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]); 405 | parse_name([], _Name) -> error. 406 | 407 | 408 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 409 | 410 | %% Register a name on a port and ask about port nr 411 | get_port_nr(Config) when is_list(Config) -> 412 | port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]). 413 | 414 | %% Register with slow write and ask about port nr 415 | slow_get_port_nr(Config) when is_list(Config) -> 416 | port_request([?EPMD_PORT_PLEASE2_REQ,d,$f,d,$o,d,$o]). 417 | 418 | 419 | % Internal function used above 420 | 421 | port_request(M) -> 422 | Port = 1042, 423 | {ok,RSock} = register_node("foo", Port), 424 | {ok,Sock} = connect(), 425 | ok = send(Sock,[size16(M),M]), 426 | case recv_until_sock_closes(Sock) of 427 | {ok, Resp} -> 428 | close(RSock), 429 | {ok,Rec} = parse_port2_resp(Resp), 430 | Port = Rec#node_info.port, 431 | ok; 432 | Other -> 433 | close(RSock), 434 | io:format("recv on sock ~w: ~p~n", [Sock,Other]), 435 | throw({error,Other}) 436 | end, 437 | ok. 438 | 439 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 440 | 441 | %% Unregister name of other node 442 | unregister_others_name_1(Config) when is_list(Config) -> 443 | {ok,RSock} = register_node("foo"), 444 | {ok,Sock} = connect(), 445 | M = [?EPMD_STOP_REQ,"foo"], 446 | ok = send(Sock,[size16(M),M]), 447 | R = "STOPPED", 448 | {ok,R} = recv(Sock,length(R)), 449 | ok = close(RSock), 450 | ok. 451 | 452 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 453 | 454 | %% Unregister name of other node 455 | unregister_others_name_2(Config) when is_list(Config) -> 456 | {ok,Sock} = connect(), 457 | M = [?EPMD_STOP_REQ,"xxx42"], 458 | ok = send(Sock,[size16(M),M]), 459 | R = "NOEXIST", 460 | {ok,R} = recv(Sock,length(R)), 461 | ok. 462 | 463 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 464 | 465 | %% Register too many, clean and redo 10 times 466 | register_overflow(Config) when is_list(Config) -> 467 | ct:timetrap({minutes, 10}), 468 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 469 | Count = length(Conn), 470 | ok = unregister_many(Conn), 471 | sleep(?MEDIUM_PAUSE), 472 | io:format("Limit was ~w names, now reg/unreg all 10 times~n", [Count]), 473 | ok = register_repeat(Count), 474 | sleep(?MEDIUM_PAUSE), 475 | ok = rregister_repeat(Count), 476 | sleep(?MEDIUM_PAUSE), 477 | ok = register_repeat(Count), 478 | sleep(?MEDIUM_PAUSE), 479 | ok = rregister_repeat(Count), 480 | sleep(?MEDIUM_PAUSE), 481 | ok = register_repeat(Count), 482 | sleep(?MEDIUM_PAUSE), 483 | ok = rregister_repeat(Count), 484 | sleep(?MEDIUM_PAUSE), 485 | ok = register_repeat(Count), 486 | sleep(?MEDIUM_PAUSE), 487 | ok = rregister_repeat(Count), 488 | sleep(?MEDIUM_PAUSE), 489 | ok = register_repeat(Count), 490 | sleep(?MEDIUM_PAUSE), 491 | ok = rregister_repeat(Count), 492 | ok. 493 | 494 | register_repeat(Count) -> 495 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 496 | ok = unregister_many(Conn), 497 | if 498 | length(Conn) == Count -> 499 | ok; 500 | true -> 501 | error 502 | end. 503 | 504 | rregister_repeat(Count) -> 505 | Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), 506 | ok = unregister_many(lists:reverse(Conn)), 507 | if 508 | length(Conn) == Count -> 509 | ok; 510 | true -> 511 | error 512 | end. 513 | 514 | % Return count of successful registrations 515 | 516 | register_many(I, N, _Prefix) when I > N -> 517 | io:format("Done with all ~n", []), []; 518 | register_many(I, N, Prefix) -> 519 | Name = gen_name(Prefix, I), 520 | Port = ?DUMMY_PORT + I, % Just make it up 521 | case register_node(Name, Port) of 522 | {ok,Sock} -> 523 | [{Name,Port,Sock} | register_many(I + 1, N, Prefix)]; 524 | Any -> 525 | io:format("Can't register: ~w of 1..~w ~w~n", [Name,N,Any]), 526 | [] 527 | end. 528 | 529 | unregister_many([]) -> 530 | ok; 531 | unregister_many([{Name,_Port,Sock} | Socks]) -> 532 | case close(Sock) of 533 | ok -> 534 | unregister_many(Socks); 535 | Any -> 536 | io:format("Can't unregister: ~w reason ~w~n", [Name,Any]), 537 | error 538 | end. 539 | 540 | gen_name(Str,Int) -> 541 | Str ++ integer_to_list(Int). 542 | 543 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 544 | 545 | %% Open but send no data 546 | no_data(Config) when is_list(Config) -> 547 | {skip, "Unsure"}. 548 | % {ok,Sock} = connect(), 549 | % sleep(?LONG_PAUSE), 550 | % closed = recv(Sock,1), 551 | % ok. 552 | 553 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 554 | 555 | %% Send one byte only 556 | one_byte(Config) when is_list(Config) -> 557 | {skip, "Unsure"}. 558 | % {ok,Sock} = connect(), 559 | % ok = send(Sock,[0]), 560 | % sleep(?LONG_PAUSE), 561 | % closed = recv(Sock,1), 562 | % ok. 563 | 564 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 565 | 566 | %% Send packet size only 567 | two_bytes(Config) when is_list(Config) -> 568 | {skip, "Unsure"}. 569 | % {ok,Sock} = connect(), 570 | % ok = send(Sock,[put16(3)]), 571 | % sleep(?LONG_PAUSE), 572 | % closed = recv(Sock,1), 573 | % ok. 574 | 575 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 576 | 577 | %% Got only part of a packet 578 | partial_packet(Config) when is_list(Config) -> 579 | {skip, "Unsure"}. 580 | % {ok,Sock} = connect(), 581 | % ok = send(Sock,[put16(100),"only a few bytes"]), 582 | % sleep(?LONG_PAUSE), 583 | % closed = recv(Sock,1), 584 | % ok. 585 | 586 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 587 | 588 | %% Invalid zero packet size 589 | zero_length(Config) when is_list(Config) -> 590 | {ok,Sock} = connect(), 591 | ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), 592 | sleep(?MEDIUM_PAUSE), 593 | closed = recv(Sock,1), 594 | ok. 595 | 596 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 597 | 598 | %% Invalid large packet 599 | too_large(Config) when is_list(Config) -> 600 | {ok,Sock} = connect(), 601 | Size = 63000, 602 | M = lists:duplicate(Size, $z), 603 | ok = send(Sock,[put16(Size),M]), 604 | sleep(?MEDIUM_PAUSE), 605 | % With such a large packet, even the writes can fail as the 606 | % daemon closes before everything is delivered -> econnaborted 607 | case recv(Sock,1) of 608 | closed -> ok; 609 | {error,econnaborted} -> ok; 610 | Other -> exit({unexpected,Other}) 611 | end. 612 | 613 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 614 | 615 | %% Try to register but not enough data 616 | alive_req_too_small_1(Config) when is_list(Config) -> 617 | {ok,Sock} = connect(), 618 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), 619 | put16(5),put16(0)], 620 | ok = send(Sock, [size16(M), M]), 621 | sleep(?MEDIUM_PAUSE), 622 | closed = recv(Sock,1), 623 | ok. 624 | 625 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 626 | 627 | %% Try to register but not enough data 628 | alive_req_too_small_2(Config) when is_list(Config) -> 629 | {ok,Sock} = connect(), 630 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), put16(5)], 631 | ok = send(Sock, [size16(M), M]), 632 | sleep(?MEDIUM_PAUSE), 633 | closed = recv(Sock,1), 634 | ok. 635 | 636 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 637 | 638 | %%Try to register but node name too large 639 | alive_req_too_large(Config) when is_list(Config) -> 640 | {ok,Sock} = connect(), 641 | L = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 642 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 643 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 644 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 645 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 646 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 647 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 648 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 649 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 650 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 651 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 652 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], 653 | S = length(lists:flatten(L)), 654 | M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), 655 | put16(5), put16(S),L,put16(0)], 656 | ok = send(Sock, [size16(M), M]), 657 | sleep(?MEDIUM_PAUSE), 658 | Wat = recv(Sock,2), 659 | io:format("too_large ~p~n", [Wat]), 660 | {ok,[?EPMD_ALIVE2_RESP,1]} = Wat, 661 | ok. 662 | 663 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 664 | 665 | %% Check that an empty extra is prefixed by a two byte length 666 | returns_valid_empty_extra(Config) when is_list(Config) -> 667 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []), 668 | {ok,#node_info{extra=[]}} = port_please_v2("foo"), 669 | ok = close(Sock), 670 | ok. 671 | 672 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 673 | 674 | %% Check a populated extra with embedded null characters 675 | returns_valid_populated_extra_with_nulls(Config) when is_list(Config) -> 676 | {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"), 677 | {ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"), 678 | ok = close(Sock), 679 | ok. 680 | 681 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 682 | 683 | %% Test that epmd -names prints registered nodes to stdout 684 | names_stdout(Config) when is_list(Config) -> 685 | {ok,Sock} = register_node("foobar"), 686 | Data = os:cmd(?EPMD ++ " " ++ epmd_port_arg() ++ " -names"), 687 | io:format("NAMES: ~p~n", [Data]), 688 | {match,_} = re:run(Data, "^epmd: up and running", [multiline]), 689 | {match,_} = re:run(Data, "^name foobar at port", [multiline]), 690 | ok = close(Sock), 691 | ok. 692 | 693 | epmd_port_arg() -> 694 | "-port " ++ integer_to_list(?PORT). 695 | 696 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 697 | 698 | %% Test security vulnerability in fake extra lengths in alive2_req 699 | buffer_overrun_1(Config) when is_list(Config) -> 700 | true = alltrue([hostile(N) || N <- lists:seq(1,10000)]), 701 | ok. 702 | 703 | %% Test security vulnerability in fake extra lengths in alive2_req 704 | buffer_overrun_2(Config) when is_list(Config) -> 705 | [false | Rest] = [hostile2(N) || N <- lists:seq(255*4,10000)], 706 | true = alltrue(Rest), 707 | ok. 708 | hostile(N) -> 709 | try 710 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16>>, 711 | S = size(Bin), 712 | {ok,E}=connect_sturdy(), 713 | gen_tcp:send(E,[<>,Bin]), 714 | closed = recv(E,1), 715 | gen_tcp:close(E), 716 | true 717 | catch 718 | _:_ -> 719 | false 720 | end. 721 | hostile2(N) -> 722 | try 723 | B2 = list_to_binary(lists:duplicate(N,255)), 724 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16,B2/binary>>, 725 | S = size(Bin), 726 | {ok,E}=connect_sturdy(), 727 | gen_tcp:send(E,[<>,Bin]), 728 | Z = recv(E,2), 729 | gen_tcp:close(E), 730 | (Z =:= closed) or (Z =:= {ok, [$y,1]}) 731 | catch 732 | _A:_B -> 733 | false 734 | end. 735 | 736 | alltrue([]) -> 737 | true; 738 | alltrue([true|T]) -> 739 | alltrue(T); 740 | alltrue([_|_]) -> 741 | false. 742 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 743 | 744 | %% Ensure that we cannot register throug a nonlocal connection 745 | no_nonlocal_register(Config) when is_list(Config) -> 746 | case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of 747 | {SSH,Name} when is_list(Name), is_list(SSH) -> 748 | do_no_nonlocal_register(Config,Name); 749 | {false,_} -> 750 | {skip, "No ssh command found to create proxy"}; 751 | _ -> 752 | {skip, "No ssh_proxy_host configured in ts.config"} 753 | end. 754 | do_no_nonlocal_register(Config,SSHHost) when is_list(Config) -> 755 | ProxyPort = proxy_port(), 756 | ok = ssh_proxy(SSHHost,ProxyPort), 757 | Res = try 758 | Name = "gurka_" 759 | %++ 760 | %integer_to_list(A1)++"_"++ 761 | %integer_to_list(A2)++"_"++ 762 | %integer_to_list(A3)++"_"++ 763 | %integer_to_list(A4) 764 | , 765 | Bname = list_to_binary(Name), 766 | NameS = byte_size(Bname), 767 | Bin= <<$x:8,4747:16,$M:8,0:8,5:16, 768 | 5:16,NameS:16,Bname/binary, 769 | 0:16>>, 770 | S = size(Bin), 771 | {ok, E} = connect("localhost",ProxyPort,passive), 772 | gen_tcp:send(E,[<>,Bin]), 773 | closed = recv(E,1), 774 | gen_tcp:close(E), 775 | true 776 | catch 777 | _:_ -> 778 | false 779 | end, 780 | %erlang:display(Res), 781 | true = Res, 782 | ok. 783 | 784 | %% Ensure that we cannot kill through nonlocal connection 785 | no_nonlocal_kill(Config) when is_list(Config) -> 786 | case {os:find_executable("ssh"),ct:get_config(ssh_proxy_host)} of 787 | {SSH,Name} when is_list(Name), is_list(SSH) -> 788 | do_no_nonlocal_kill(Config,Name); 789 | {false,_} -> 790 | {skip, "No ssh command found to create proxy"}; 791 | _ -> 792 | {skip, "No ssh_proxy_host configured in ts.config"} 793 | end. 794 | do_no_nonlocal_kill(Config,SSHHost) when is_list(Config) -> 795 | ProxyPort = proxy_port(), 796 | ok = ssh_proxy(SSHHost,ProxyPort), 797 | Res = try 798 | {ok, E} = connect("localhost",ProxyPort,passive), 799 | M = [?EPMD_KILL_REQ], 800 | send(E, [size16(M), M]), 801 | closed = recv(E,2), 802 | gen_tcp:close(E), 803 | sleep(?MEDIUM_PAUSE), 804 | {ok, E2} = connect("localhost",ProxyPort,passive), 805 | gen_tcp:close(E2), 806 | true 807 | catch 808 | _:_ -> 809 | false 810 | end, 811 | %erlang:display(Res), 812 | true = Res, 813 | ok. 814 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 815 | 816 | %% Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check 817 | no_live_killing(Config) when is_list(Config) -> 818 | {ok,RSock} = register_node("foo"), 819 | {ok,Sock} = connect(), 820 | M = [?EPMD_KILL_REQ], 821 | ok = send(Sock,[size16(M),M]), 822 | {ok,"NO"} = recv(Sock,2), 823 | close(Sock), 824 | {ok,Sock2} = connect(), 825 | M2 = [?EPMD_STOP_REQ,"foo"], 826 | ok = send(Sock2,[size16(M2),M2]), 827 | closed = recv(Sock2,1), 828 | close(Sock2), 829 | close(RSock), 830 | 831 | % don't kill this node 832 | % rethink erlang:halt/0 833 | % sleep(?MEDIUM_PAUSE), 834 | % {ok,Sock3} = connect(), 835 | % M3 = [?EPMD_KILL_REQ], 836 | % ok = send(Sock3,[size16(M3),M3]), 837 | % {ok,"OK"} = recv(Sock3,2), 838 | % close(Sock3), 839 | 840 | ok. 841 | 842 | %% Check for regression - don't make zombie from node which 843 | %% sends TCP RST at wrong time 844 | socket_reset_before_alive2_reply_is_written(Config) when is_list(Config) -> 845 | %% We can't use send_req/1 directly as we want to do inet:setopts/2 846 | %% on our socket. 847 | {ok, Sock} = connect(), 848 | 849 | %% Issuing close/1 on such socket will result in immediate RST packet. 850 | ok = inet:setopts(Sock, [{linger, {true, 0}}]), 851 | 852 | Req = alive2_req(4711, 77, 0, 5, 5, "test", []), 853 | ok = send(Sock, [size16(Req), Req]), 854 | 855 | timer:sleep(500), %% Wait for the first 1/2 of delay_write before closing 856 | ok = close(Sock), 857 | 858 | timer:sleep(500 + ?SHORT_PAUSE), %% Wait for the other 1/2 of delay_write 859 | 860 | %% Wait another delay_write interval, due to delay doubling in epmd. 861 | %% Should be removed when this is issue is fixed there. 862 | timer:sleep(5000), 863 | 864 | {ok, SockForNames} = connect_active(), 865 | io:format("sockfornames ~p~n", [SockForNames]), 866 | 867 | %% And there should be no stuck nodes 868 | {ok, []} = do_get_names(SockForNames), 869 | ok = close(SockForNames), 870 | ok. 871 | 872 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 873 | % Start an ssh channel to simulate remote access 874 | 875 | proxy_port() -> 876 | ?PORT+1. 877 | 878 | ssh_proxy(SSHHost,ProxyPort) -> 879 | Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")), 880 | % Requires proxy to be a unix host with the command 'read' accessible 881 | osrun("ssh -L "++integer_to_list(ProxyPort)++":"++Host++":" 882 | ++integer_to_list(?PORT)++" "++SSHHost++" read"). 883 | 884 | 885 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 886 | % Start an external process 887 | 888 | osrun(Cmd) -> 889 | io:format("osrun(~p)~n",[Cmd]), 890 | spawn_link(fun() -> flusher(Cmd) end), 891 | ok. 892 | 893 | flusher(Cmd) -> 894 | Port = open_port({spawn, Cmd}, [stderr_to_stdout]), 895 | link(Port), 896 | flusher(). 897 | 898 | flusher() -> 899 | receive 900 | {_,{data,Msg}} -> 901 | io:format("from epmd: ~s~n", [Msg]), 902 | flusher(); 903 | Msg -> 904 | io:format("from epmd (unexpected): ~p~n", [Msg]), 905 | flusher() 906 | end. 907 | 908 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 909 | % Wrappers of TCP functions 910 | 911 | % These functions is the interface for connect. 912 | % Passive mode is the default 913 | 914 | connect() -> 915 | connect("localhost",?PORT, passive). 916 | 917 | connect(Addr) -> 918 | connect(Addr,?PORT, passive). 919 | 920 | connect_active() -> 921 | connect("localhost",?PORT, active). 922 | 923 | %% Retry after 2 seconds, to avoid TIME_WAIT socket exhaust. 924 | connect_sturdy() -> 925 | connect("localhost",?PORT, passive, 2000, 3). 926 | 927 | % Try a few times before giving up 928 | connect(Addr, Port, Mode) -> 929 | connect(Addr, Port, Mode, ?CONN_SLEEP, ?CONN_RETRY). 930 | connect(Addr, Port, Mode, Sleep, Retry) -> 931 | case connect_repeat(Addr, Retry, Port, Mode, Sleep) of 932 | {ok,Sock} -> 933 | {ok,Sock}; 934 | {error,timeout} -> 935 | timeout; 936 | {error,Reason} -> 937 | io:format("connect: error: ~w~n",[Reason]), 938 | error; 939 | Any -> 940 | io:format("connect: unknown message: ~w~n",[Any]), 941 | exit(1) 942 | end. 943 | 944 | 945 | % Try a few times before giving up. Pause a small time between 946 | % each try. 947 | 948 | connect_repeat(Addr, 1, Port, Mode, _Sleep) -> 949 | connect_mode(Addr,Port, Mode); 950 | connect_repeat(Addr,Retry, Port, Mode, Sleep) -> 951 | case connect_mode(Addr,Port, Mode) of 952 | {ok,Sock} -> 953 | {ok,Sock}; 954 | {error,Reason} -> 955 | io:format("connect: error: ~w sleeping for ~w~n",[Reason,Sleep]), 956 | timer:sleep(Sleep), 957 | connect_repeat(Addr, Retry - 1, Port, Mode, 2*Sleep); 958 | Any -> 959 | io:format("connect: unknown message: ~w~n",[Any]), 960 | exit(1) 961 | end. 962 | 963 | connect_mode(Addr,Port, active) -> 964 | gen_tcp:connect(Addr, Port, [{packet, 0}], ?CONN_TIMEOUT); 965 | connect_mode(Addr, Port, passive) -> 966 | gen_tcp:connect(Addr, Port, [{packet, 0}, {active, false}], 967 | ?CONN_TIMEOUT). 968 | 969 | 970 | close(Sock) -> 971 | case gen_tcp:close(Sock) of 972 | {error,_} -> 973 | error; 974 | ok -> 975 | ok; 976 | Any -> 977 | io:format("unknown message: ~w~n",[Any]), 978 | exit(1) 979 | end. 980 | 981 | recv(Sock, Len) -> 982 | recv(Sock, Len, ?RECV_TIMEOUT). 983 | 984 | recv(Sock, Len, Timeout) -> 985 | case gen_tcp:recv(Sock, Len, Timeout) of 986 | {ok,[]} -> % Should not be the case 987 | recv(Sock, 1, 1); % any longer 988 | {ok,Data} -> 989 | {ok,Data}; 990 | {error,timeout} -> 991 | timeout; 992 | {error,closed} -> 993 | closed; 994 | {error,_}=Error -> 995 | Error; 996 | Any -> 997 | io:format("unknown message: ~w~n",[Any]), 998 | exit(1) 999 | end. 1000 | 1001 | %% Send data to socket. The list can be non flat and contain 1002 | %% the atom 'd' or tuple {d,Seconds} where this is delay 1003 | %% put in between the sent characters. 1004 | 1005 | send(Sock, SendSpec) -> 1006 | case send(SendSpec, [], Sock) of 1007 | {ok,[]} -> 1008 | ok; 1009 | {ok,RevBytes} -> 1010 | send_direct(Sock, lists:reverse(RevBytes)); 1011 | Any -> 1012 | Any 1013 | end. 1014 | 1015 | 1016 | % If an error, return immediately 1017 | % Collect real characters in the first argument to form 1018 | % a string to send. Only perform "actions", like a delay, 1019 | % when this argument is empty. 1020 | 1021 | send([], RevBytes, _Sock) -> 1022 | {ok,RevBytes}; 1023 | send([Byte | Spec], RevBytes, Sock) when is_integer(Byte) -> 1024 | send(Spec, [Byte | RevBytes], Sock); 1025 | send([List | Spec], RevBytes, Sock) when is_list(List) -> 1026 | case send(List, RevBytes, Sock) of 1027 | {ok,Left} -> 1028 | send(Spec, Left, Sock); 1029 | Other -> 1030 | Other 1031 | end; 1032 | send([d | Spec], RevBytes, Sock) -> 1033 | send([{d,1000} | Spec], RevBytes, Sock); 1034 | send([{d,S} | Spec], RevBytes, Sock) -> 1035 | case send_direct(Sock, lists:reverse(RevBytes)) of 1036 | ok -> 1037 | timer:sleep(S), 1038 | send(Spec, [], Sock); 1039 | Any -> 1040 | Any 1041 | end. 1042 | 1043 | %%%% 1044 | 1045 | send_direct(Sock, Bytes) -> 1046 | case gen_tcp:send(Sock, Bytes) of 1047 | ok -> 1048 | ok; 1049 | {error, closed} -> 1050 | closed; 1051 | {error, _Reason} -> 1052 | error; 1053 | Any -> 1054 | io:format("unknown message: ~w~n",[Any]), 1055 | Any 1056 | end. 1057 | 1058 | send_req(Req) -> 1059 | send_req(Req, "localhost"). 1060 | send_req(Req,Addr) -> 1061 | case connect(Addr) of 1062 | {ok,Sock} -> 1063 | case send(Sock, [size16(Req), Req]) of 1064 | ok -> 1065 | {ok,Sock}; 1066 | Other -> 1067 | io:format("Failed to send ~w on sock ~w: ~w~n", 1068 | [Req,Sock,Other]), 1069 | error 1070 | end; 1071 | Other -> 1072 | io:format("Connect failed when sending ~w: ~p~n", 1073 | [Req, Other]), 1074 | error 1075 | end. 1076 | 1077 | recv_until_sock_closes(Sock) -> 1078 | recv_until_sock_closes_2(Sock,[]). 1079 | 1080 | recv_until_sock_closes_2(Sock,AccData) -> 1081 | case recv(Sock,0) of 1082 | {ok,Data} -> 1083 | recv_until_sock_closes_2(Sock,AccData++Data); 1084 | closed -> 1085 | {ok,AccData}; 1086 | Other -> 1087 | Other 1088 | end. 1089 | 1090 | sleep(MilliSeconds) -> 1091 | timer:sleep(MilliSeconds). 1092 | 1093 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1094 | 1095 | put16(N) -> 1096 | [N bsr 8, N band 16#ff]. 1097 | 1098 | size16(List) -> 1099 | N = flat_count(List, 0), 1100 | [N bsr 8, N band 16#ff]. 1101 | 1102 | flat_count([H|T], N) when is_integer(H) -> 1103 | flat_count(T, N+1); 1104 | flat_count([H|T], N) when is_list(H) -> 1105 | flat_count(T, flat_count(H, N)); 1106 | flat_count([_|T], N) -> 1107 | flat_count(T, N); 1108 | flat_count([], N) -> N. 1109 | 1110 | 1111 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1112 | 1113 | alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> 1114 | Utf8Name = unicode:characters_to_binary(Name), 1115 | [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot, 1116 | put16(HVsn), put16(LVsn), 1117 | put16(size(Utf8Name)), binary_to_list(Utf8Name), 1118 | size16(Extra), Extra]. 1119 | --------------------------------------------------------------------------------