diff options
Diffstat (limited to 'erts')
-rw-r--r-- | erts/doc/src/epmd.xml | 252 | ||||
-rw-r--r-- | erts/epmd/src/Makefile.in | 25 | ||||
-rw-r--r-- | erts/epmd/src/epmd.c | 53 | ||||
-rw-r--r-- | erts/epmd/src/epmd.h | 21 | ||||
-rw-r--r-- | erts/epmd/src/epmd_cli.c | 8 | ||||
-rw-r--r-- | erts/epmd/src/epmd_int.h | 9 | ||||
-rw-r--r-- | erts/epmd/src/epmd_srv.c | 212 | ||||
-rw-r--r-- | erts/epmd/test/epmd_SUITE.erl | 356 |
8 files changed, 640 insertions, 296 deletions
diff --git a/erts/doc/src/epmd.xml b/erts/doc/src/epmd.xml index 796ab3820b..f01cf90a36 100644 --- a/erts/doc/src/epmd.xml +++ b/erts/doc/src/epmd.xml @@ -4,7 +4,7 @@ <comref> <header> <copyright> - <year>1996</year><year>2009</year> + <year>1996</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -31,9 +31,23 @@ <rev>A</rev> <file>epmd.xml</file> </header> + <com>epmd</com> - <comsummary>Erlang Port Mapper Daemon </comsummary> + <comsummary> + <p>Erlang Port Mapper Daemon</p> + <taglist> + <tag><c><![CDATA[epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon] [-relaxed_command_check]]]></c></tag> + <item> + <p>Starts the port mapper daemon</p> + </item> + <tag><c><![CDATA[epmd [-d|-debug] [-port No] [-names|-kill|-stop Name]]]></c></tag> + <item> + <p>Communicates with a running port mapper daemon</p> + </item> + </taglist> + </comsummary> <description> + <p>This daemon acts as a name server on all hosts involved in distributed Erlang computations. When an Erlang node starts, the node has a name and it obtains an address from the host @@ -46,48 +60,171 @@ The job of the <c><![CDATA[epmd]]></c> daemon is to keep track of which node name listens on which address. Hence, <c><![CDATA[epmd]]></c> map symbolic node names to machine addresses.</p> - <p>The daemon is started automatically by the Erlang start-up script.</p> - <p>The program <c><![CDATA[epmd]]></c> can also be used for a variety of other - purposes, for example checking the DNS (Domain Name System) - configuration of a host.</p> + + <p>The TCP/IP <c>epmd</c> daemon actually only keeps track of + the <c>Name</c> (first) part of an Erlang node name, the <c>Host</c> + part (whatever is after the <c><![CDATA[@]]></c> is implicit in the + node name where the <c>epmd</c> daemon was actually contacted, + as is the IP address where the Erlang node can be + reached. Consistent and correct TCP naming services are + therefore required for an Erlang network to function + correctly.</p> + + <taglist> + <tag>Starting the port mapper daemon</tag> + <item> + + <p>The daemon is started automatically by the <c>erl</c> + command if the node is to be distributed and there is no + running instance present. If automatically launched, + environment variables has to be used to alter the behavior of + the daemon. See the <seealso + marker="#environment_variables">Environment + variables</seealso> section below.</p> + + <p>If the -daemon argument is not given, the + <c><![CDATA[epmd]]></c> runs as a normal program with the + controlling terminal of the shell in which it is + started. Normally, it should run as a daemon.</p> + + <p>Regular start-up options are described in the + <seealso marker="#daemon_flags">Regular options</seealso> + section below.</p> + + <p>The <c>DbgExtra</c> options are described in the + <seealso marker="#debug_flags">DbgExtra options</seealso> + section below.</p> + + </item> + <tag>Communicating with a running port mapper daemon</tag> + <item> + + <p>Communicating with the running epmd daemon by means of the + <c>epmd</c> program is done primarily for debugging + purposes.</p> + + <p>The different queries are described in the <seealso + marker="#interactive_flags">Interactive options</seealso> + section below.</p> + + </item> + </taglist> </description> - <funcs> - <func> - <name>epmd [-daemon] </name> - <fsummary>Start a name server as a daemon</fsummary> - <desc> - <p>Starts a name server as a daemon. If it has no argument, the - <c><![CDATA[epmd]]></c> runs as a normal program with the controlling terminal - of the shell in which it is started. Normally, it should run as a - daemon.</p> - </desc> - </func> - <func> - <name>epmd -names</name> - <fsummary>Request the names of the registered Erlang nodes on this host</fsummary> - <desc> - <p>Requests the names of the local Erlang nodes <c><![CDATA[epmd]]></c> has - registered.</p> - </desc> - </func> - <func> - <name>epmd -kill</name> - <fsummary>Kill the <c><![CDATA[epmd]]></c>process</fsummary> - <desc> - <p>Kills the <c><![CDATA[epmd]]></c> process.</p> - </desc> - </func> - <func> - <name>epmd -help</name> - <fsummary>List options</fsummary> - <desc> - <p>Write short info about the usage including some debugging - options not listed here.</p> - </desc> - </func> - </funcs> + <section> + <marker id="daemon_flags"></marker> + <title>Regular options</title> + + <p>These options are available when starting the actual name server. The name server is normally started automatically by the <c>erl</c> command (if not already available), but it can also be started at i.e. system start-up.</p> + <taglist> + <tag><c><![CDATA[-port No]]></c></tag> + <item> + <p>Let this instance of epmd listen to another TCP port than + default 4369. This can be also be set using the + <c><![CDATA[ERL_EPMD_PORT]]></c> environment variable, see the + section <seealso marker="#environment_variables">Environment + variables</seealso> below</p> + </item> + <tag><c><![CDATA[-d | -debug]]></c></tag> + <item> + + <p>Enable debug output. The more <c>-d</c> flags given, the more + debug output you will get (to a certain limit). This option is + most useful when the epmd daemon is not started as a daemon.</p> + </item> + <tag><c><![CDATA[-daemon]]></c></tag> + <item> + <p>Start epmd detached from the controlling terminal. Logging will end up in syslog when available and correctly configured. If the epmd daemon is started at boot, this option should definitely be used. It is also used when the <c>erl</c> command automatically starts <c>epmd</c>.</p> + </item> + <tag><c><![CDATA[-relaxed_command_check]]></c></tag> + <item> + <p>Start the epmd program with relaxed command checking (mostly for backward compatibility). This affects the following:</p> + <list type="bulleted"> + <item> + <p>With relaxed command checking, the <c>epmd</c> daemon can be killed from the localhost with i.e. <c>epmd -kill</c> even if there are active nodes registered. Normally only daemons with an empty node database can be killed with the <c>epmd -kill</c> command.</p> + </item> + <item> + <p>The <c>epmd -stop</c> command (and the corresponding messages to epmd, as can be given using <c>erl_interface/ei</c>) is normally always ignored, as it opens up for strange situation when two nodes of the same name can be alive at the same time. A node unregisters itself by just closing the connection to epmd, why the <c>stop</c> command was only intended for use in debugging situations.</p> + <p>With relaxed command checking enabled, you can forcibly unregister live nodes.</p> + </item> + </list> + <p>Relaxed command checking can also be enabled by setting the environment variable <c>ERL_EPMD_RELAXED_COMMAND_CHECK</c> prior to starting <c>epmd</c>.</p> + <p>Only use relaxed command checking on systems with very limited interactive usage.</p> + </item> + </taglist> + </section> <section> + <marker id="debug_flags"></marker> + <title>DbgExtra options</title> + <p>These options are purely for debugging and testing epmd clients, they should not be used in normal operation.</p> + + <taglist> + <tag><c><![CDATA[-packet_timeout Seconds]]></c></tag> + <item> + <p>Set the number of seconds a connection can be + inactive before epmd times out and closes the + connection (default 60).</p> + </item> + <tag><c><![CDATA[-delay_accept Seconds]]></c></tag> + <item> + <p>To simulate a busy server you can insert a delay between epmd + gets notified about that a new connection is requested and + when the connections gets accepted.</p> + </item> + <tag><c><![CDATA[-delay_write Seconds]]></c></tag> + <item> + <p>Also a simulation of a busy server. Inserts + a delay before a reply is sent.</p> + </item> + </taglist> + </section> + <section> + <marker id="interactive_flags"></marker> + <title>Interactive options</title> + <p>These options make <c>epmd</c> run as an interactive command displaying the results of sending queries ta an already running instance of <c>epmd</c>. The epmd contacted is always on the local node, but the <c>-port</c> option can be used to select between instances if several are running using different port on the host.</p> + <taglist> + <tag><c><![CDATA[-port No]]></c></tag> + <item> + <p>Contacts the <c>epmd</c> listening on the given TCP port number + (default 4369). This can be also be set using the + <c><![CDATA[ERL_EPMD_PORT]]></c> environment variable, see the + section <seealso marker="#environment_variables">Environment + variables</seealso> below</p> + </item> + <tag><c><![CDATA[-names]]></c></tag> + <item> + <p>List names registered with the currently running epmd</p> + </item> + <tag><c><![CDATA[-kill]]></c></tag> + <item> + <p>Kill the currently running <c>epmd</c>.</p> + + <p>Killing the running <c>epmd</c> is only allowed if <c>epmd + -names</c> show an empty database or + <c>-relaxed_command_check</c> was given when the running + instance of <c>epmd</c> was started. Note that + <c>-relaxed_command_check</c> is given when starting the + daemon that is to accept killing when it has live nodes + registered. When running epmd interactively, + <c>-relaxed_command_check</c> has no effect. A daemon that is + started without relaxed command checking has to be killed + using i.e. signals or some other OS specific method if it has + active clients registered.</p> + </item> + <tag><c><![CDATA[-stop Name]]></c></tag> + <item> + <p>Forcibly unregister a live node from <c>epmd</c>'s database</p> + + <p>This command can only be used when contacting <c>epmd</c> + instances started with the <c>-relaxed_command_check</c> + flag. Note that relaxed command checking has to be enabled for + the <c>epmd</c> daemon contacted, When running epmd + interactively, + <c>-relaxed_command_check</c> has no effect.</p> + </item> + </taglist> + </section> + <section> <marker id="environment_variables"></marker> <title>Environment variables</title> <taglist> @@ -99,6 +236,15 @@ independent clusters of nodes, to co-exist on the same host. All nodes in a cluster must use the same epmd port number.</p> </item> + <tag><c><![CDATA[ERL_EPMD_RELAXED_COMMAND_CHECK]]></c></tag> + <item> + <p>If set prior to start, the <c>epmd</c> daemon will behave + as if the <c>-relaxed_command_check</c> option was given at + start-up. If consequently setting this option before starting + the Erlang virtual machine, the automatically started + <c>epmd</c> will accept the <c>-kill</c> and <c>-stop</c> + commands without restrictions.</p> + </item> </taglist> </section> @@ -116,5 +262,29 @@ silently be ignored. </p> </section> + <section> + <title>Access restrictions</title> + <p>The <c>epmd</c> daemon accepts messages from both localhost and + remote hosts. However, only the query commands are answered (and + acted upon) if the query comes from a remote host. It is always an + error to try to register a nodename if the client is not a process + located on the same host as the <c>epmd</c> instance is running on, + why such requests are considered hostile and the connection is + immediately closed.</p> + + <p>The queries accepted from remote nodes are:</p> + <list type="bulleted"> + <item> + <p>Port queries - i.e. on which port does the node with a given + name listen</p> + </item> + <item> + <p>Name listing - i.e. give a list of all names registered on + the host</p> + </item> + </list> + <p>To restrict access further, firewall software has to be used.</p> + </section> + </comref> diff --git a/erts/epmd/src/Makefile.in b/erts/epmd/src/Makefile.in index 498756b468..70a54fe46e 100644 --- a/erts/epmd/src/Makefile.in +++ b/erts/epmd/src/Makefile.in @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# Copyright Ericsson AB 1998-2010. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -49,15 +49,27 @@ include ../epmd.mk BINDIR = $(ERL_TOP)/bin/$(TARGET) OBJDIR = $(ERL_TOP)/erts/obj$(TYPEMARKER)/$(TARGET) +ERTS_INCL = -I$(ERL_TOP)/erts/include \ + -I$(ERL_TOP)/erts/include/$(TARGET) \ + -I$(ERL_TOP)/erts/include/internal \ + -I$(ERL_TOP)/erts/include/internal/$(TARGET) + +# On windows we always need reentrant libraries. +ifeq ($(TARGET),win32) +ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal_r$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@ +else +ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@ -lm +endif CC = @CC@ WFLAGS = @WFLAGS@ -CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS) +CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS) $(ERTS_INCL) LD = @LD@ -LIBS = @LIBS@ +LIBS = @LIBS@ $(ERTS_INTERNAL_LIBS) LDFLAGS = @LDFLAGS@ + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -90,7 +102,7 @@ EPMD_OBJS = $(OBJDIR)/epmd.o \ #--------------------------------- -all: $(BINDIR)/$(EPMD) +all: erts_lib $(BINDIR)/$(EPMD) docs: @@ -109,9 +121,12 @@ clean: $(BINDIR)/$(EPMD): $(EPMD_OBJS) $(PURIFY) $(LD) $(LDFLAGS) -o $@ $(EPMD_OBJS) $(LIBS) -$(OBJDIR)/%.o: %.c +$(OBJDIR)/%.o: %.c epmd.h epmd_int.h $(CC) $(CFLAGS) $(EPMD_FLAGS) -o $@ -c $< +erts_lib: + cd $(ERL_TOP)/erts/lib_src && $(MAKE) $(TYPE) + include $(ERL_TOP)/make/otp_release_targets.mk release_spec: all diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c index 9c2ce065bb..65ff0cd6b2 100644 --- a/erts/epmd/src/epmd.c +++ b/erts/epmd/src/epmd.c @@ -23,6 +23,7 @@ #endif #include "epmd.h" /* Renamed from 'epmd_r4.h' */ #include "epmd_int.h" +#include "erl_printf.h" #ifdef HAVE_STDLIB_H # include <stdlib.h> @@ -33,6 +34,7 @@ static void usage(EpmdVars *); static void run_daemon(EpmdVars*); static int get_port_no(void); +static int check_relaxed(void); #ifdef __WIN32__ static int has_console(void); #endif @@ -161,6 +163,7 @@ int main(int argc, char** argv) g->silent = 0; g->is_daemon = 0; + g->brutal_kill = check_relaxed(); g->packet_timeout = CLOSE_TIMEOUT; /* Default timeout */ g->delay_accept = 0; g->delay_write = 0; @@ -196,6 +199,9 @@ int main(int argc, char** argv) } else if (strcmp(argv[0], "-daemon") == 0) { g->is_daemon = 1; argv++; argc--; + } else if (strcmp(argv[0], "-relaxed_command_check") == 0) { + g->brutal_kill = 1; + argv++; argc--; } else if (strcmp(argv[0], "-kill") == 0) { if (argc == 1) kill_epmd(g); @@ -388,9 +394,10 @@ static void run_daemon(EpmdVars *g) static void usage(EpmdVars *g) { fprintf(stderr, "usage: epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon]\n"); - fprintf(stderr, " [-d|-debug] [-port No] [-names|-kill|-stop name]\n\n"); - fprintf(stderr, "See the Erlang epmd manual page for info about the usage.\n"); - fprintf(stderr, "The -port and DbgExtra options are\n\n"); + fprintf(stderr, " [-relaxed_command_check]\n"); + fprintf(stderr, " epmd [-d|-debug] [-port No] [-names|-kill|-stop name]\n\n"); + fprintf(stderr, "See the Erlang epmd manual page for info about the usage.\n\n"); + fprintf(stderr, "Regular options\n"); fprintf(stderr, " -port No\n"); fprintf(stderr, " Let epmd listen to another port than default %d\n", EPMD_PORT_NO); @@ -400,8 +407,16 @@ static void usage(EpmdVars *g) fprintf(stderr, " the standard error stream. It will shorten\n"); fprintf(stderr, " the number of saved used node names to 5.\n\n"); fprintf(stderr, " If you give more than one debug flag you may\n"); - fprintf(stderr, " get more debugging information.\n\n"); - fprintf(stderr, " -packet_timout Seconds\n"); + fprintf(stderr, " get more debugging information.\n"); + fprintf(stderr, " -daemon\n"); + fprintf(stderr, " Start epmd detached (as a daemon)\n"); + fprintf(stderr, " -relaxed_command_check\n"); + fprintf(stderr, " Allow this instance of epmd to be killed with\n"); + fprintf(stderr, " epmd -kill even if there " + "are registered nodes.\n"); + fprintf(stderr, " Also allows forced unregister (epmd -stop).\n"); + fprintf(stderr, "\nDbgExtra options\n"); + fprintf(stderr, " -packet_timeout Seconds\n"); fprintf(stderr, " Set the number of seconds a connection can be\n"); fprintf(stderr, " inactive before epmd times out and closes the\n"); fprintf(stderr, " connection (default 60).\n\n"); @@ -413,6 +428,18 @@ static void usage(EpmdVars *g) fprintf(stderr, " -delay_write Seconds\n"); fprintf(stderr, " Also a simulation of a busy server. Inserts\n"); fprintf(stderr, " a delay before a reply is sent.\n"); + fprintf(stderr, "\nInteractive options\n"); + fprintf(stderr, " -names\n"); + fprintf(stderr, " List names registered with the currently " + "running epmd\n"); + fprintf(stderr, " -kill\n"); + fprintf(stderr, " Kill the currently runniing epmd\n"); + fprintf(stderr, " (only allowed if -names show empty database or\n"); + fprintf(stderr, " -relaxed_command_check was given when epmd was started).\n"); + fprintf(stderr, " -stop Name\n"); + fprintf(stderr, " Forcibly unregisters a name with epmd\n"); + fprintf(stderr, " (only allowed if -relaxed_command_check was given when \n"); + fprintf(stderr, " epmd was started).\n"); epmd_cleanup_exit(g,1); } @@ -432,20 +459,20 @@ static void usage(EpmdVars *g) * args... Arguments to print out according to the format * */ - +#define DEBUG_BUFFER_SIZE 2048 static void dbg_gen_printf(int onsyslog,int perr,int from_level, EpmdVars *g,const char *format, va_list args) { time_t now; char *timestr; - char buf[2048]; + char buf[DEBUG_BUFFER_SIZE]; if (g->is_daemon) { #ifndef NO_SYSLOG if (onsyslog) { - vsprintf(buf, format, args); + erts_vsnprintf(buf, DEBUG_BUFFER_SIZE, format, args); syslog(LOG_ERR,"epmd: %s",buf); } #endif @@ -456,9 +483,10 @@ static void dbg_gen_printf(int onsyslog,int perr,int from_level, time(&now); timestr = (char *)ctime(&now); - sprintf(buf, "epmd: %.*s: ", (int) strlen(timestr)-1, timestr); + erts_snprintf(buf, DEBUG_BUFFER_SIZE, "epmd: %.*s: ", + (int) strlen(timestr)-1, timestr); len = strlen(buf); - vsprintf(buf + len, format, args); + erts_vsnprintf(buf + len, DEBUG_BUFFER_SIZE - len, format, args); if (perr == 1) perror(buf); else @@ -545,4 +573,9 @@ static int get_port_no(void) char* port_str = getenv("ERL_EPMD_PORT"); return (port_str != NULL) ? atoi(port_str) : EPMD_PORT_NO; } +static int check_relaxed(void) +{ + char* port_str = getenv("ERL_EPMD_RELAXED_COMMAND_CHECK"); + return (port_str != NULL) ? 1 : 0; +} diff --git a/erts/epmd/src/epmd.h b/erts/epmd/src/epmd.h index 9e939ee38e..5d6e9ac165 100644 --- a/erts/epmd/src/epmd.h +++ b/erts/epmd/src/epmd.h @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * Copyright Ericsson AB 1998-2010. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -17,21 +17,18 @@ * %CopyrightEnd% */ -/* The port number is now defined in a makefile */ +/* The port number is defined in a makefile */ /* Definitions of message codes */ -#define EPMD_ALIVE_REQ 'a' -#define EPMD_ALIVE_OK_RESP 'Y' -#define EPMD_PORT_REQ 'p' +/* Registration and queries */ +#define EPMD_ALIVE2_REQ 'x' +#define EPMD_PORT2_REQ 'z' +#define EPMD_ALIVE2_RESP 'y' +#define EPMD_PORT2_RESP 'w' #define EPMD_NAMES_REQ 'n' + +/* Interactive client command codes */ #define EPMD_DUMP_REQ 'd' #define EPMD_KILL_REQ 'k' #define EPMD_STOP_REQ 's' - -/* New epmd messages */ - -#define EPMD_ALIVE2_REQ 'x' /* 120 */ -#define EPMD_PORT2_REQ 'z' /* 122 */ -#define EPMD_ALIVE2_RESP 'y' /* 121 */ -#define EPMD_PORT2_RESP 'w' /* 119 */ diff --git a/erts/epmd/src/epmd_cli.c b/erts/epmd/src/epmd_cli.c index 2aed861390..7c60ba0420 100644 --- a/erts/epmd/src/epmd_cli.c +++ b/erts/epmd/src/epmd_cli.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * Copyright Ericsson AB 1998-2010. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -42,7 +42,11 @@ void kill_epmd(EpmdVars *g) epmd_cleanup_exit(g,1); } if ((rval = read_fill(fd,buf,2)) == 2) { - printf("Killed\n"); + if (buf[0] == 'O' && buf[1] == 'K') { + printf("Killed\n"); + } else { + printf("Killing not allowed - living nodes in database.\n"); + } epmd_cleanup_exit(g,0); } else if (rval < 0) { printf("epmd: failed to read answer from local epmd\n"); diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h index 5ead553f36..c2558d52a1 100644 --- a/erts/epmd/src/epmd_int.h +++ b/erts/epmd/src/epmd_int.h @@ -1,3 +1,4 @@ +/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */ /* * %CopyrightBegin% * @@ -245,8 +246,10 @@ typedef struct { int fd; /* File descriptor */ - unsigned open:1; /* TRUE if open */ - unsigned keep:1; /* Don't close when sent reply */ + unsigned char open; /* TRUE if open */ + unsigned char keep; /* Don't close when sent reply */ + unsigned char local_peer; /* The peer of this connection is via + loopback interface */ unsigned got; /* # of bytes we have got */ unsigned want; /* Number of bytes we want */ char *buf; /* The remaining buffer */ @@ -286,6 +289,7 @@ typedef struct { int debug; int silent; int is_daemon; + int brutal_kill; unsigned packet_timeout; unsigned delay_accept; unsigned delay_write; @@ -307,6 +311,7 @@ void epmd_call(EpmdVars*,int); void run(EpmdVars*); void epmd_cleanup_exit(EpmdVars*, int); int epmd_conn_close(EpmdVars*,Connection*); +void stop_cli(EpmdVars *g, char *name); #ifdef DONT_USE_MAIN int start_epmd(char *,char *,char *,char *,char *,char *,char *,char *,char *,char *); diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c index c836bf0bb7..df4d1a5715 100644 --- a/erts/epmd/src/epmd_srv.c +++ b/erts/epmd/src/epmd_srv.c @@ -47,38 +47,6 @@ * | Length | Request | * +--------+---------+ * - * In all but one case there is only one request for each connection made - * to this server so we can safely close the socket after sending the - * reply. The exception is ALIVE_REQ where we keep the connection - * open without sending any data. When we receive a "close" this is - * an indication that the Erlang node was terminated. The termination - * may have been "normal" or caused by a crash. The operating system - * ensure that the connection is closed either way. - * - * Reading is done non-blocking, i.e. we call a "read" only if we are - * told by the "select" function that there are data to read. - * - * Two databases are used: One node database where the registered names - * of the nodes are stored, and one connection database where the state - * of sockets and the data buffers is stored. - * - * Incomplete packets are thrown away after a timout. The Erlang node - * doing the request is responsible for completing in it in a reasonable time. - * - * Note that if the server gets busy it may not have time to - * process all requests for connection. The "accept()" function - * will on most operating systems silently refuse to accept more - * than 5 outstanding requests. It is the client's responsibility - * to retry the request a number of times with random time interval. - * The "-debug" flag will insert a delay so you can test this - * behaviour. - * - * FIXME: In this code we assume that the packets we send on each - * socket is so small that a "write()" never block - * - * FIXME: We never restarts a read or write that was terminated - * by an interrupt. Do we need to? - * */ /* We use separate data structures for node names and connections @@ -98,7 +66,6 @@ static int conn_open(EpmdVars*,int); static int conn_close_fd(EpmdVars*,int); static void node_init(EpmdVars*); -static Node *node_reg(EpmdVars*,char*,int,int); static Node *node_reg2(EpmdVars*,char*, int, int, unsigned char, unsigned char, int, int, int, char*); static int node_unreg(EpmdVars*,char*); static int node_unreg_sock(EpmdVars*,int); @@ -303,11 +270,9 @@ static void do_read(EpmdVars *g,Connection *s) s->fd,val); dbg_print_buf(g,s->buf,val); - /* FIXME: Shouldn't be needed to close down.... */ node_unreg_sock(g,s->fd); epmd_conn_close(g,s); } - /* FIXME: We always close, probably the right thing to do */ return; } @@ -405,6 +370,8 @@ static int do_accept(EpmdVars *g,int listensock) return conn_open(g,msgsock); } +/* buf is actually one byte larger than bsize, + giving place for null termination */ static void do_request(g, fd, s, buf, bsize) EpmdVars *g; int fd; @@ -415,117 +382,23 @@ static void do_request(g, fd, s, buf, bsize) char wbuf[OUTBUF_SIZE]; /* Buffer for writing */ int i; - /* - * Terminate packet as a C string. Needed for requests received from Erlang - * nodes with lower version than R3A. - */ - - buf[bsize] = '\0'; + buf[bsize] = '\0'; /* Needed for strcmp in PORT2 and STOP requests + buf is always large enough */ switch (*buf) { - case EPMD_ALIVE_REQ: - dbg_printf(g,1,"** got ALIVE_REQ"); - - /* The packet has the format "axxyyyyyy" where xx is port, given - in network byte order, and yyyyyy is symname, possibly null - terminated. */ - - if (buf[bsize - 1] == '\000') /* Skip null termination */ - bsize--; - - if (bsize <= 3) - { - dbg_printf(g,0,"packet to small for request ALIVE_REQ (%d)", bsize); - return; - } - - for (i = 3; i < bsize; i++) - if (buf[i] == '\000') - { - dbg_printf(g,0,"node name contains ascii 0 in ALIVE_REQ"); - return; - } - - { - Node *node; - int eport; - char *name = &buf[3]; /* points to node name */ - - eport = get_int16(&buf[1]); - - if ((node = node_reg(g, name, fd, eport)) == NULL) - return; - - wbuf[0] = EPMD_ALIVE_OK_RESP; - put_int16(node->creation, wbuf+1); - - if (g->delay_write) /* Test of busy server */ - sleep(g->delay_write); - - if (reply(g, fd, wbuf, 3) != 3) - { - dbg_tty_printf(g,1,"failed to send ALIVE_OK_RESP for \"%s\"",name); - return; - } - - dbg_tty_printf(g,1,"** sent ALIVE_OK_RESP for \"%s\"",name); - s->keep = EPMD_TRUE; /* Don't close on inactivity */ - } - break; - - case EPMD_PORT_REQ: - dbg_printf(g,1,"** got PORT_REQ"); - - if (buf[bsize - 1] == '\000') /* Skip null termination */ - bsize--; - - if (bsize <= 1) - { - dbg_printf(g,0,"packet to small for request PORT_REQ (%d)", bsize); - return; - } - - for (i = 1; i < bsize; i++) - if (buf[i] == '\000') - { - dbg_printf(g,0,"node name contains ascii 0 in PORT_REQ"); - return; - } - - { - char *name = &buf[1]; /* Points to node name */ - Node *node; - - for (node = g->nodes.reg; node; node = node->next) - { - if (strcmp(node->symname, name) == 0) - { - put_int16(node->port,wbuf); - if (reply(g, fd, wbuf, 2) != 2) - { - dbg_tty_printf(g,1,"failed to send PORT_RESP for %s: %d", - name,node->port); - return; - } - dbg_tty_printf(g,1,"** sent PORT_RESP for %s: %d", - name,node->port); - return; - } - } - dbg_tty_printf(g,1,"Closed on PORT_REQ for %s",name); - } - /* FIXME: How about an answer if no port? Is a close enough? */ - break; - case EPMD_ALIVE2_REQ: dbg_printf(g,1,"** got ALIVE2_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"ALIVE2_REQ from non local address"); + return; + } /* The packet has the format "axxyyyyyy" where xx is port, given in network byte order, and yyyyyy is symname, possibly null terminated. */ - if (bsize <= 13) + if (bsize <= 13) /* at least one character for the node name */ { dbg_printf(g,0,"packet to small for request ALIVE2_REQ (%d)",bsize); return; @@ -548,7 +421,17 @@ static void do_request(g, fd, s, buf, bsize) highvsn = get_int16(&buf[5]); lowvsn = get_int16(&buf[7]); namelen = get_int16(&buf[9]); + if (namelen + 13 > bsize) { + dbg_printf(g,0,"Node name size error in ALIVE2_REQ"); + return; + } extralen = get_int16(&buf[11+namelen]); + + if (extralen + namelen + 13 > bsize) { + dbg_printf(g,0,"Extra info size error in ALIVE2_REQ"); + return; + } + for (i = 11 ; i < 11 + namelen; i ++) if (buf[i] == '\000') { dbg_printf(g,0,"node name contains ascii 0 in ALIVE2_REQ"); @@ -679,6 +562,10 @@ static void do_request(g, fd, s, buf, bsize) case EPMD_DUMP_REQ: dbg_printf(g,1,"** got DUMP_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"DUMP_REQ from non local address"); + return; + } { Node *node; @@ -728,7 +615,19 @@ static void do_request(g, fd, s, buf, bsize) break; case EPMD_KILL_REQ: + if (!s->local_peer) { + dbg_printf(g,0,"KILL_REQ from non local address"); + return; + } dbg_printf(g,1,"** got KILL_REQ"); + + if (!g->brutal_kill && (g->nodes.reg != NULL)) { + dbg_printf(g,0,"Disallowed KILL_REQ, live nodes"); + if (reply(g, fd,"NO",2) != 2) + dbg_printf(g,0,"failed to send reply to KILL_REQ"); + return; + } + if (reply(g, fd,"OK",2) != 2) dbg_printf(g,0,"failed to send reply to KILL_REQ"); dbg_tty_printf(g,1,"epmd killed"); @@ -738,6 +637,15 @@ static void do_request(g, fd, s, buf, bsize) case EPMD_STOP_REQ: dbg_printf(g,1,"** got STOP_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"STOP_REQ from non local address"); + return; + } + if (!g->brutal_kill) { + dbg_printf(g,0,"Disallowed STOP_REQ, no relaxed_command_check"); + return; + } + if (bsize <= 1 ) { dbg_printf(g,0,"packet too small for request STOP_REQ (%d)",bsize); @@ -825,6 +733,14 @@ static int conn_open(EpmdVars *g,int fd) for (i = 0; i < g->max_conn; i++) { if (g->conn[i].open == EPMD_FALSE) { + struct sockaddr_in si; +#ifdef HAVE_SOCKLEN_T + socklen_t st; +#else + int st; +#endif + st = sizeof(si); + g->active_conn++; s = &g->conn[i]; @@ -834,6 +750,20 @@ static int conn_open(EpmdVars *g,int fd) s->fd = fd; s->open = EPMD_TRUE; s->keep = EPMD_FALSE; + + /* Determine if connection is from localhost */ + if (getpeername(s->fd,(struct sockaddr*) &si,&st) || + st < sizeof(si)) { + /* Failure to get peername is regarder as non local host */ + s->local_peer = EPMD_FALSE; + } else { + s->local_peer = + ((((unsigned) ntohl(si.sin_addr.s_addr)) & 0xFF000000U) == + 0x7F000000U); /* Only 127.x.x.x allowed, no false positives */ + } + dbg_tty_printf(g,2,(s->local_peer) ? "Local peer connected" : + "Non-local peer connected"); + s->want = 0; /* Currently unknown */ s->got = 0; s->mod_time = current_time(g); /* Note activity */ @@ -990,11 +920,6 @@ static int node_unreg_sock(EpmdVars *g,int fd) * Perhaps use the oldest or something. */ -static Node *node_reg(EpmdVars *g,char *name,int fd, int port) -{ - return node_reg2(g, name, fd, port, 0, 0, 0, 0, 0, NULL); -} - static Node *node_reg2(EpmdVars *g, char* name, int fd, @@ -1020,6 +945,11 @@ static Node *node_reg2(EpmdVars *g, dbg_printf(g,0,"node name is too long (%d) %s", strlen(name), name); return NULL; } + if (extralen > MAXSYMLEN) + { + dbg_printf(g,0,"extra data is too long (%d) %s", strlen(name), name); + return NULL; + } /* Fail if it is already registered */ diff --git a/erts/epmd/test/epmd_SUITE.erl b/erts/epmd/test/epmd_SUITE.erl index 91e09faf75..da69412e12 100644 --- a/erts/epmd/test/epmd_SUITE.erl +++ b/erts/epmd/test/epmd_SUITE.erl @@ -63,7 +63,13 @@ alive_req_too_large/1, returns_valid_empty_extra/1, - returns_valid_populated_extra_with_nulls/1 + returns_valid_populated_extra_with_nulls/1, + buffer_overrun/1, + buffer_overrun_1/1, + buffer_overrun_2/1, + no_nonlocal_register/1, + no_nonlocal_kill/1, + no_live_killing/1 ]). @@ -82,11 +88,8 @@ -define(REG_REPEAT_LIM,1000). % Message codes in epmd protocol --define(EPMD_ALIVE_REQ, $a). -define(EPMD_ALIVE2_REQ, $x). --define(EPMD_ALIVE_OK_RESP, $Y). -define(EPMD_ALIVE2_RESP, $y). --define(EPMD_PORT_REQ, $p). -define(EPMD_PORT_PLEASE2_REQ, $z). -define(EPMD_PORT2_RESP, $w). -define(EPMD_NAMES_REQ, $n). @@ -124,7 +127,15 @@ all(suite) -> alive_req_too_large, returns_valid_empty_extra, - returns_valid_populated_extra_with_nulls + returns_valid_populated_extra_with_nulls, + + buffer_overrun, + %buffer_overrun_1, + %buffer_overrun_2, + + no_nonlocal_register, + no_nonlocal_kill, + no_live_killing ]. %% @@ -132,7 +143,7 @@ all(suite) -> %% init_per_testcase(_Func, Config) -> - Dog = test_server:timetrap(?SHORT_TEST_TIMEOUT), + Dog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), cleanup(), [{watchdog, Dog} | Config]. @@ -148,7 +159,7 @@ register_name(doc) -> ["Register a name"]; register_name(suite) -> []; -register_name(Config) when list(Config) -> +register_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line ok = close(Sock), % Unregister @@ -158,7 +169,7 @@ register_names_1(doc) -> ["Register and unregister two nodes"]; register_names_1(suite) -> []; -register_names_1(Config) when list(Config) -> +register_names_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), @@ -170,7 +181,7 @@ register_names_2(doc) -> ["Register and unregister two nodes"]; register_names_2(suite) -> []; -register_names_2(Config) when list(Config) -> +register_names_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), @@ -182,7 +193,7 @@ register_duplicate_name(doc) -> ["Two nodes with the same name"]; register_duplicate_name(suite) -> []; -register_duplicate_name(Config) when list(Config) -> +register_duplicate_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line error = register_node("foobar"), @@ -192,22 +203,9 @@ register_duplicate_name(Config) when list(Config) -> % Internal function to register a node name, no close, i.e. unregister register_node(Name) -> - register_node(Name,?DUMMY_PORT). - -register_node(Name, Port) -> - case send_req([?EPMD_ALIVE_REQ, put16(Port), Name]) of - {ok,Sock} -> - case recv(Sock,3) of - {ok, [?EPMD_ALIVE_OK_RESP,_D1,_D0]} -> - {ok,Sock}; - Other -> - test_server:format("recv on sock ~w: ~p~n", - [Sock,Other]), - error - end; - error -> - error - end. + register_node_v2(?DUMMY_PORT,$M,0,5,5,Name,""). +register_node(Name,Port) -> + register_node_v2(Port,$M,0,5,5,Name,""). register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> Req = [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot, @@ -254,7 +252,7 @@ parse_port2_resp(Resp) -> hvsn=HVsn,lvsn=LVsn, node_name=binary_to_list(NodeName), extra=binary_to_list(Extra)}}; - Other -> + _Other -> test_server:format("invalid port2 resp: ~p~n", [Resp]), error @@ -266,7 +264,7 @@ name_with_null_inside(doc) -> ["Register a name with a null char in it"]; name_with_null_inside(suite) -> []; -name_with_null_inside(Config) when list(Config) -> +name_with_null_inside(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line error = register_node("foo\000bar"), ok. @@ -277,11 +275,9 @@ name_null_terminated(doc) -> ["Register a name with terminating null byte"]; name_null_terminated(suite) -> []; -name_null_terminated(Config) when list(Config) -> +name_null_terminated(Config) when is_list(Config) -> ?line ok = epmdrun(), - ?line {ok,Sock} = register_node("foobar\000"), - ?line error = register_node("foobar"), - ?line ok = close(Sock), % Unregister + ?line error = register_node("foobar\000"), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -290,7 +286,7 @@ stupid_names_req(doc) -> ["Read names from epmd in a stupid way"]; stupid_names_req(suite) -> []; -stupid_names_req(Config) when list(Config) -> +stupid_names_req(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), @@ -394,15 +390,15 @@ get_port_nr(doc) -> ["Register a name on a port and ask about port nr"]; get_port_nr(suite) -> []; -get_port_nr(Config) when list(Config) -> - port_request([?EPMD_PORT_REQ,"foo"]). +get_port_nr(Config) when is_list(Config) -> + port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]). slow_get_port_nr(doc) -> ["Register with slow write and ask about port nr"]; slow_get_port_nr(suite) -> []; -slow_get_port_nr(Config) when list(Config) -> - port_request([?EPMD_PORT_REQ,d,$f,d,$o,d,$o]). +slow_get_port_nr(Config) when is_list(Config) -> + port_request([?EPMD_PORT_PLEASE2_REQ,d,$f,d,$o,d,$o]). % Internal function used above @@ -413,9 +409,18 @@ port_request(M) -> ?line {ok,RSock} = register_node("foo", Port), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[size16(M),M]), - R = put16(Port), - ?line {ok,R} = recv(Sock, length(R)), - ?line ok = close(RSock), + ?line case recv_until_sock_closes(Sock) of + {ok, Resp} -> + ?line close(RSock), + ?line {ok,Rec} = parse_port2_resp(Resp), + ?line Port = Rec#node_info.port, + ok; + Other -> + ?line close(RSock), + ?line test_server:format("recv on sock ~w: ~p~n", + [Sock,Other]), + ?line throw({error,Other}) + end, ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -424,8 +429,8 @@ unregister_others_name_1(doc) -> ["Unregister name of other node"]; unregister_others_name_1(suite) -> []; -unregister_others_name_1(Config) when list(Config) -> - ?line ok = epmdrun(), +unregister_others_name_1(Config) when is_list(Config) -> + ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,RSock} = register_node("foo"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"foo"], @@ -441,8 +446,8 @@ unregister_others_name_2(doc) -> ["Unregister name of other node"]; unregister_others_name_2(suite) -> []; -unregister_others_name_2(Config) when list(Config) -> - ?line ok = epmdrun(), +unregister_others_name_2(Config) when is_list(Config) -> + ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"xxx42"], ?line ok = send(Sock,[size16(M),M]), @@ -456,7 +461,7 @@ register_overflow(doc) -> ["Register too many, clean and redo 10 times"]; register_overflow(suite) -> []; -register_overflow(Config) when list(Config) -> +register_overflow(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?LONG_TEST_TIMEOUT), @@ -546,7 +551,7 @@ no_data(doc) -> ["Open but send no data"]; no_data(suite) -> []; -no_data(Config) when list(Config) -> +no_data(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), sleep(?LONG_PAUSE), @@ -559,7 +564,7 @@ one_byte(doc) -> ["Send one byte only"]; one_byte(suite) -> []; -one_byte(Config) when list(Config) -> +one_byte(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0]), @@ -573,7 +578,7 @@ two_bytes(doc) -> ["Send packet size only"]; two_bytes(suite) -> []; -two_bytes(Config) when list(Config) -> +two_bytes(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(3)]), @@ -587,7 +592,7 @@ partial_packet(doc) -> ["Got only part of a packet"]; partial_packet(suite) -> []; -partial_packet(Config) when list(Config) -> +partial_packet(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(100),"only a few bytes"]), @@ -601,7 +606,7 @@ zero_length(doc) -> ["Invalid zero packet size"]; zero_length(suite) -> []; -zero_length(Config) when list(Config) -> +zero_length(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), @@ -615,15 +620,20 @@ too_large(doc) -> ["Invalid large packet"]; too_large(suite) -> []; -too_large(Config) when list(Config) -> +too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), Size = 63000, M = lists:duplicate(Size, $z), ?line ok = send(Sock,[put16(Size),M]), sleep(?MEDIUM_PAUSE), - ?line closed = recv(Sock,1), - ok. + % With such a large packet, even the writes can fail as the + % daemon closes before everything is delivered -> econnaborted + case recv(Sock,1) of + closed -> ok; + {error,econnaborted} -> ok; + Other -> exit({unexpected,Other}) + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -631,10 +641,11 @@ alive_req_too_small_1(doc) -> ["Try to register but not enough data"]; alive_req_too_small_1(suite) -> []; -alive_req_too_small_1(Config) when list(Config) -> +alive_req_too_small_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), - M = [?EPMD_ALIVE_REQ, 42], + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5),put16(0)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), @@ -646,10 +657,11 @@ alive_req_too_small_2(doc) -> ["Try to register but not enough data"]; alive_req_too_small_2(suite) -> []; -alive_req_too_small_2(Config) when list(Config) -> +alive_req_too_small_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), - M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT)], + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), @@ -661,7 +673,7 @@ alive_req_too_large(doc) -> ["Try to register but node name too large"]; alive_req_too_large(suite) -> []; -alive_req_too_large(Config) when list(Config) -> +alive_req_too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), L = [ @@ -678,10 +690,12 @@ alive_req_too_large(Config) when list(Config) -> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ], - M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT), L], + S = length(lists:flatten(L)), + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5), put16(S),L,put16(0)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), - ?line closed = recv(Sock,1), + ?line {ok,[?EPMD_ALIVE2_RESP,1]} = recv(Sock,2), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -690,7 +704,7 @@ returns_valid_empty_extra(doc) -> ["Check that an empty extra is prefixed by a two byte length"]; returns_valid_empty_extra(suite) -> []; -returns_valid_empty_extra(Config) when list(Config) -> +returns_valid_empty_extra(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []), ?line {ok,#node_info{extra=[]}} = port_please_v2("foo"), @@ -703,7 +717,7 @@ returns_valid_populated_extra_with_nulls(doc) -> ["Check a populated extra with embedded null characters"]; returns_valid_populated_extra_with_nulls(suite) -> []; -returns_valid_populated_extra_with_nulls(Config) when list(Config) -> +returns_valid_populated_extra_with_nulls(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"), ?line {ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"), @@ -711,6 +725,167 @@ returns_valid_populated_extra_with_nulls(Config) when list(Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +buffer_overrun(suite) -> + [buffer_overrun_1,buffer_overrun_2]. + +buffer_overrun_1(suite) -> + []; +buffer_overrun_1(doc) -> + ["Test security vulnerability in fake extra lengths in alive2_req"]; +buffer_overrun_1(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line true = alltrue([hostile(N) || N <- lists:seq(1,10000)]), + ok. +buffer_overrun_2(suite) -> + []; +buffer_overrun_2(doc) -> + ["Test security vulnerability in fake extra lengths in alive2_req"]; +buffer_overrun_2(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line [false | Rest] = [hostile2(N) || N <- lists:seq(255,10000)], + ?line true = alltrue(Rest), + ok. +hostile(N) -> + try + Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16>>, + S = size(Bin), + {ok,E}=connect_sturdy(), + gen_tcp:send(E,[<<S:16>>,Bin]), + closed = recv(E,1), + gen_tcp:close(E), + true + catch + _:_ -> + false + end. +hostile2(N) -> + try + B2 = list_to_binary(lists:duplicate(N,255)), + Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16,B2/binary>>, + S = size(Bin), + {ok,E}=connect_sturdy(), + gen_tcp:send(E,[<<S:16>>,Bin]), + Z = recv(E,2), + gen_tcp:close(E), + (Z =:= closed) or (Z =:= {ok, [$y,1]}) + catch + _A:_B -> + false + end. + +alltrue([]) -> + true; +alltrue([true|T]) -> + alltrue(T); +alltrue([_|_]) -> + false. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_nonlocal_register(suite) -> + []; +no_nonlocal_register(doc) -> + ["Ensure that we cannot register throug a nonlocal connection"]; +no_nonlocal_register(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Ifs} = inet:getiflist(), + ?line Addr0 = [ inet:ifget(I, [addr]) || I <- Ifs ], + ?line Addr1 = [ A || {ok,[{addr,A}]} <- Addr0], + ?line Addr = lists:filter(fun({127,_,_,_}) -> + false; + (_) -> + true + end,Addr1), + %% Now we should have all non loopback interface addresses, + %% none should accept a alive2 registration. + ?line Res = lists:map(fun(Ad={A1,A2,A3,A4}) -> + try + Name = "gurka_"++ + integer_to_list(A1)++"_"++ + integer_to_list(A2)++"_"++ + integer_to_list(A3)++"_"++ + integer_to_list(A4), + Bname = list_to_binary(Name), + NameS = byte_size(Bname), + ?line Bin= <<$x:8,4747:16,$M:8,0:8,5:16, + 5:16,NameS:16,Bname/binary, + 0:16>>, + ?line S = size(Bin), + {ok, E} = connect(Ad), + gen_tcp:send(E,[<<S:16>>,Bin]), + closed = recv(E,1), + gen_tcp:close(E), + true + catch + _:_ -> + false + end + end, Addr), + erlang:display(Res), + ?line true = alltrue(Res), + ok. + +no_nonlocal_kill(suite) -> + []; +no_nonlocal_kill(doc) -> + ["Ensure that we cannot kill through nonlocal connection"]; +no_nonlocal_kill(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Ifs} = inet:getiflist(), + ?line Addr0 = [ inet:ifget(I, [addr]) || I <- Ifs ], + ?line Addr1 = [ A || {ok,[{addr,A}]} <- Addr0], + ?line Addr = lists:filter(fun({127,_,_,_}) -> + false; + (_) -> + true + end,Addr1), + %% Now we should have all non loopback interface addresses, + %% none should accept a alive2 registration. + ?line Res = lists:map(fun(Ad) -> + try + {ok, E} = connect(Ad), + M = [?EPMD_KILL_REQ], + send(E, [size16(M), M]), + closed = recv(E,2), + gen_tcp:close(E), + sleep(?MEDIUM_PAUSE), + {ok, E2} = connect(Ad), + gen_tcp:close(E2), + true + catch + _:_ -> + false + end + end, Addr), + erlang:display(Res), + ?line true = alltrue(Res), + ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_live_killing(doc) -> + ["Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check"]; +no_live_killing(suite) -> + []; +no_live_killing(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,RSock} = register_node("foo"), + ?line {ok,Sock} = connect(), + ?line M = [?EPMD_KILL_REQ], + ?line ok = send(Sock,[size16(M),M]), + ?line {ok,"NO"} = recv(Sock,2), + ?line close(Sock), + ?line {ok,Sock2} = connect(), + ?line M2 = [?EPMD_STOP_REQ,"foo"], + ?line ok = send(Sock2,[size16(M2),M2]), + ?line closed = recv(Sock2,1), + ?line close(Sock2), + ?line close(RSock), + ?line sleep(?MEDIUM_PAUSE), + ?line {ok,Sock3} = connect(), + ?line M3 = [?EPMD_KILL_REQ], + ?line ok = send(Sock3,[size16(M3),M3]), + ?line {ok,"OK"} = recv(Sock3,2), + ?line close(Sock3), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Terminate all tests with killing epmd. cleanup() -> @@ -731,16 +906,24 @@ cleanup() -> % Normal debug start of epmd epmdrun() -> + epmdrun([]). +epmdrun(Args) -> case os:find_executable(epmd) of false -> {error, {could_not_find_epmd_in_path}}; Path -> - epmdrun(Path) + epmdrun(Path,Args) end. -epmdrun(Epmd) -> +epmdrun(Epmd,Args0) -> %% test_server:format("epmdrun() => Epmd = ~p",[Epmd]), - osrun(Epmd ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). + Args = case Args0 of + [] -> + []; + O -> + " "++O + end, + osrun(Epmd ++ Args ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -753,20 +936,27 @@ osrun(Cmd) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Wrappers of TCP functions -% These two functions is the interface for connect. +% These functions is the interface for connect. % Passive mode is the default connect() -> - connect(?PORT, passive). + connect("localhost",?PORT, passive). + +connect(Addr) -> + connect(Addr,?PORT, passive). connect_active() -> - connect(?PORT, active). + connect("localhost",?PORT, active). +%% Retry after 15 seconds, to avoid TIME_WAIT socket exhaust. +connect_sturdy() -> + connect("localhost",?PORT, passive, 15000, 3). % Try a few times before giving up - -connect(Port, Mode) -> - case connect_repeat(?CONN_RETRY, Port, Mode) of +connect(Addr, Port, Mode) -> + connect(Addr, Port, Mode, ?CONN_SLEEP, ?CONN_RETRY). +connect(Addr, Port, Mode, Sleep, Retry) -> + case connect_repeat(Addr, Retry, Port, Mode, Sleep) of {ok,Sock} -> {ok,Sock}; {error,timeout} -> @@ -783,25 +973,25 @@ connect(Port, Mode) -> % Try a few times before giving up. Pause a small time between % each try. -connect_repeat(1, Port, Mode) -> - connect_mode(Port, Mode); -connect_repeat(Retry, Port, Mode) -> - case connect_mode(Port, Mode) of +connect_repeat(Addr, 1, Port, Mode, _Sleep) -> + connect_mode(Addr,Port, Mode); +connect_repeat(Addr,Retry, Port, Mode, Sleep) -> + case connect_mode(Addr,Port, Mode) of {ok,Sock} -> {ok,Sock}; {error,Reason} -> test_server:format("connect: error: ~w~n",[Reason]), - timer:sleep(?CONN_SLEEP), - connect_repeat(Retry - 1, Port, Mode); + timer:sleep(Sleep), + connect_repeat(Addr, Retry - 1, Port, Mode, Sleep); Any -> test_server:format("connect: unknown message: ~w~n",[Any]), exit(1) end. -connect_mode(Port, active) -> - gen_tcp:connect("localhost", Port, [{packet, 0}], ?CONN_TIMEOUT); -connect_mode(Port, passive) -> - gen_tcp:connect("localhost", Port, [{packet, 0}, {active, false}], +connect_mode(Addr,Port, active) -> + gen_tcp:connect(Addr, Port, [{packet, 0}], ?CONN_TIMEOUT); +connect_mode(Addr, Port, passive) -> + gen_tcp:connect(Addr, Port, [{packet, 0}, {active, false}], ?CONN_TIMEOUT). @@ -858,9 +1048,9 @@ send(Sock, SendSpec) -> send([], RevBytes, _Sock) -> {ok,RevBytes}; -send([Byte | Spec], RevBytes, Sock) when integer(Byte) -> +send([Byte | Spec], RevBytes, Sock) when is_integer(Byte) -> send(Spec, [Byte | RevBytes], Sock); -send([List | Spec], RevBytes, Sock) when list(List) -> +send([List | Spec], RevBytes, Sock) when is_list(List) -> case send(List, RevBytes, Sock) of {ok,Left} -> send(Spec, Left, Sock); |