diff options
Diffstat (limited to 'erts/epmd')
-rw-r--r-- | erts/epmd/Makefile | 32 | ||||
-rw-r--r-- | erts/epmd/doc/.gitignore | 0 | ||||
-rw-r--r-- | erts/epmd/epmd.mk | 70 | ||||
-rw-r--r-- | erts/epmd/src/Makefile | 22 | ||||
-rw-r--r-- | erts/epmd/src/Makefile.in | 123 | ||||
-rw-r--r-- | erts/epmd/src/epmd.c | 629 | ||||
-rw-r--r-- | erts/epmd/src/epmd.h | 37 | ||||
-rw-r--r-- | erts/epmd/src/epmd_cli.c | 127 | ||||
-rw-r--r-- | erts/epmd/src/epmd_int.h | 346 | ||||
-rw-r--r-- | erts/epmd/src/epmd_srv.c | 1254 | ||||
-rw-r--r-- | erts/epmd/test/Makefile | 80 | ||||
-rw-r--r-- | erts/epmd/test/epmd.spec | 1 | ||||
-rw-r--r-- | erts/epmd/test/epmd.spec.vxworks | 2 | ||||
-rw-r--r-- | erts/epmd/test/epmd_SUITE.erl | 835 |
14 files changed, 3558 insertions, 0 deletions
diff --git a/erts/epmd/Makefile b/erts/epmd/Makefile new file mode 100644 index 0000000000..4c1af393ac --- /dev/null +++ b/erts/epmd/Makefile @@ -0,0 +1,32 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- +SUB_DIRECTORIES = src + +SPECIAL_TARGETS = + +# ---------------------------------------------------- +# Default Subdir Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_subdir.mk diff --git a/erts/epmd/doc/.gitignore b/erts/epmd/doc/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/erts/epmd/doc/.gitignore diff --git a/erts/epmd/epmd.mk b/erts/epmd/epmd.mk new file mode 100644 index 0000000000..a73f4bc077 --- /dev/null +++ b/erts/epmd/epmd.mk @@ -0,0 +1,70 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# ------------------------------------------------ +# Server defines +# + +# EPMD port number +# 4365 - Version 4.2 TCP +# 4366 - Version 4.3 TCP +# 4368 - Version 4.4.0 - 4.6.2 TCP +# 4369 - Version 4.6.3 - 4.7.4 TCP/UDP +EPMD_PORT_NO=4369 + + +# ------------------------------------------------ +# Client defines +# + +# Node type: +# 72 = R3 hidden node +# 77 = R3 erlang node +# 104 = R4 hidden node +# 109 = R4 erlang node +# (110 = R6 nodes (explicit flags for differences between nodes)) +# +# What epmd has been told, differs very much between versions, both +# 111 and 110 seems to have been used to tell epmd, while +# the actual nodetypes has still been 104 and 109. +# EPMD does not care about this, why we move back to using +# the correct tag (an 'n') for all nodes. +# +EPMD_NODE_TYPE=110 + +# Lowest/Highest supported version of the distribution protocol: +# 0 = R3 +# 1 = R4 +# 2 = R5 ????? +# 3 = R5C +# 4 = R6 (development) +# 5 = R6 +# There was no protocol change in release R5, so we didn't need to raise +# the version number. But now that R5A is released, it's best to keep it +# this way. +# The number was inadvertently raised for R5C, so we increase it again +# for R6. +# Distribution version 4 means a) distributed monitor and b) larger references +# in the distribution format. +# In format 5, nodes can explicitly tell each other which of the above +# mentioned capabilities they can handle. +# Distribution format 5 contains the new md5 based handshake. + +EPMD_DIST_LOW=5 +EPMD_DIST_HIGH=5 + diff --git a/erts/epmd/src/Makefile b/erts/epmd/src/Makefile new file mode 100644 index 0000000000..7d586c7438 --- /dev/null +++ b/erts/epmd/src/Makefile @@ -0,0 +1,22 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# Invoke with GNU make or clearmake -C gnu. +# + +include $(ERL_TOP)/make/run_make.mk diff --git a/erts/epmd/src/Makefile.in b/erts/epmd/src/Makefile.in new file mode 100644 index 0000000000..498756b468 --- /dev/null +++ b/erts/epmd/src/Makefile.in @@ -0,0 +1,123 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk + + +ifeq ($(TYPE),debug) +PURIFY = +TYPEMARKER = .debug +TYPE_FLAGS = -DDEBUG @DEBUG_FLAGS@ +else +ifeq ($(TYPE),purify) +PURIFY = purify +TYPEMARKER = +ifeq ($(findstring ose,$(TARGET)),ose) + TYPE_FLAGS = -DPURIFY +else + TYPE_FLAGS = -O2 -DPURIFY +endif +else +PURIFY = +TYPEMARKER = +ifeq ($(findstring ose,$(TARGET)),ose) + TYPE_FLAGS = +else + TYPE_FLAGS = -O2 +endif +endif +endif + +include $(ERL_TOP)/make/$(TARGET)/otp.mk +include ../../vsn.mk +include ../epmd.mk + +BINDIR = $(ERL_TOP)/bin/$(TARGET) +OBJDIR = $(ERL_TOP)/erts/obj$(TYPEMARKER)/$(TARGET) + +CC = @CC@ +WFLAGS = @WFLAGS@ +CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS) +LD = @LD@ +LIBS = @LIBS@ +LDFLAGS = @LDFLAGS@ + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- + +# The targets +ifeq ($(findstring win32,$(TARGET)),win32) +EPMD = epmd.exe +else +EPMD = epmd +endif + +INSTALL_PROGS = $(BINDIR)/$(EPMD) + +#--------------------------------- +# Options +#--------------------------------- + +EPMD_FLAGS = -DEPMD_PORT_NO=$(EPMD_PORT_NO) + +#--------------------------------- +# source and object file information +#--------------------------------- + +EPMD_OBJS = $(OBJDIR)/epmd.o \ + $(OBJDIR)/epmd_cli.o \ + $(OBJDIR)/epmd_srv.o + +#--------------------------------- +# Build targets +#--------------------------------- + + +all: $(BINDIR)/$(EPMD) + +docs: + +clean: + rm -f $(BINDIR)/$(EPMD) + rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd.o + rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd_cli.o + rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd_srv.o + rm -f *.o + rm -f *~ core + +# +# Objects & executables +# + +$(BINDIR)/$(EPMD): $(EPMD_OBJS) + $(PURIFY) $(LD) $(LDFLAGS) -o $@ $(EPMD_OBJS) $(LIBS) + +$(OBJDIR)/%.o: %.c + $(CC) $(CFLAGS) $(EPMD_FLAGS) -o $@ -c $< + +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: all + $(INSTALL_DIR) $(RELEASE_PATH)/erts-$(VSN)/bin + $(INSTALL_PROGRAM) $(INSTALL_PROGS) $(RELEASE_PATH)/erts-$(VSN)/bin + + +release_docs_spec: + diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c new file mode 100644 index 0000000000..23ac421446 --- /dev/null +++ b/erts/epmd/src/epmd.c @@ -0,0 +1,629 @@ +/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "epmd.h" /* Renamed from 'epmd_r4.h' */ +#include "epmd_int.h" + +#ifdef _OSE_ +# include "ose.h" +# include "efs.h" +#endif + +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif + +/* forward declarations */ + +static void usage(EpmdVars *); +static void run_daemon(EpmdVars*); +static int get_port_no(void); +#ifdef __WIN32__ +static int has_console(void); +#endif + +#ifdef DONT_USE_MAIN + +static int epmd_main(int, char **, int); + +/* VxWorks fill 10 stack words with zero when a function is called + from the shell. So it is safe to have argv and argc as parameters + even if they are not given in the call. */ + +#define MAX_DEBUG 10 + +int epmd_dbg(int level,int port) /* Utility to debug epmd... */ +{ + char* argv[MAX_DEBUG+2]; + char ibuff[100]; + int argc = 0; + + argv[argc++] = "epmd"; + if(level > MAX_DEBUG) + level = MAX_DEBUG; + for(;level;--level) + argv[argc++] = "-d"; + if(port) + { + argv[argc++] = "-port"; + sprintf(ibuff,"%d",port); + argv[argc++] = ibuff; + } + argv[argc] = NULL; + + return epmd(argc,argv); + +} + +static char *mystrdup(char *s) +{ + char *r = malloc(strlen(s)+1); + strcpy(r,s); + return r; +} + +#ifdef _OSE_ + +struct args_sig { + SIGSELECT sig_no; + int argc ; + char argv[20][20]; +}; + +union SIGNAL { + SIGSELECT sig_no; + struct args_sig args; +}; + +/* Start function. It may be called from the start script as well as from + the OSE shell directly (using late start hooks). It spawns epmd as an + OSE process which calls the epmd main function. */ +int start_ose_epmd(int argc, char **argv) { + union SIGNAL *sig; + PROCESS epmd_; + OSENTRYPOINT ose_epmd; + int i; + + if(hunt("epmd", 0, &epmd_, NULL)) { + fprintf(stderr, "Warning! EPMD already exists (%u).\n", epmd_); + return 0; + } + else { + /* copy start args to signal */ + sig = alloc(sizeof(struct args_sig), 0); + sig->args.argc = argc; + for(i=0; i<argc; i++) { + strcpy((sig->args.argv)[i], argv[i]); + } + /* start epmd and send signal */ + epmd_ = create_process(OS_BG_PROC, /* processtype */ + "epmd", /* name */ + ose_epmd, /* entrypoint */ + 16383, /* stacksize */ + 20, /* priority */ + 0, /* timeslice */ + 0, /* block */ + NULL,0,0); /* not used */ + efs_clone(epmd_); + start(epmd_); + send(&sig, epmd_); +#ifdef DEBUG + printf("EPMD ID: %li\n", epmd_); +#endif + } + return 0; +} + +OS_PROCESS(ose_epmd) { + union SIGNAL *sig; + static const SIGSELECT rec_any_sig[] = { 0 }; + int i, argc; + char **argv; + + sig = receive((SIGSELECT*)rec_any_sig); + + argc = sig->args.argc; + argv = (char **)malloc((argc+1)*sizeof(char *)); + for(i=0; i<argc; i++) { + argv[i] = (char *)malloc(strlen((sig->args.argv)[i])+1); + strcpy(argv[i], (sig->args.argv)[i]); + } + argv[argc] = NULL; + free_buf(&sig); + + epmd(argc, argv); + + for(i=0; i<argc; i++) { + free(argv[i]); + } + free(argv); +} + +#else /* ifdef _OSE_ */ + +/* VxWorks start function */ +int start_epmd(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) +char *a0, *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9; +{ + char* argarr[] = {a0,a1,a2,a3,a4,a5,a6,a7,a8,a9}; + int i; + char** argv = malloc(sizeof(char *)*10); + int argvsiz = 10; + int argc = 1; + char* tmp = malloc(100); + int tmpsiz = 100; + char* pplast; + char* token; + + argv[0] = mystrdup("epmd"); + argv[1] = NULL; + + for(i=0;i<10;++i) + { + if(argarr[i] == NULL || *argarr[i] == '\0') + continue; + if(strlen(argarr[i]) >= tmpsiz) + tmp = realloc(tmp, tmpsiz = (strlen(argarr[i])+1)); + strcpy(tmp,argarr[i]); + for(token = strtok_r(tmp," ",&pplast); + token != NULL; + token = strtok_r(NULL," ",&pplast)) + { + if(argc >= argvsiz - 1) + argv = realloc(argv,sizeof(char *) * (argvsiz += 10)); + argv[argc++] = mystrdup(token); + argv[argc] = NULL; + } + } + free(tmp); + return taskSpawn("epmd",100,VX_FP_TASK,20000,epmd_main, + argc,(int) argv,1, + 0,0,0,0,0,0,0); +} + +#endif /* _OSE_ */ + + + + +int epmd(int argc, char **argv) +{ + return epmd_main(argc,argv,0); +} + +static int epmd_main(int argc, char** argv, int free_argv) +#else +int main(int argc, char** argv) +#endif /* DONT_USE_MAIN */ +{ + EpmdVars g_empd_vars; + EpmdVars *g = &g_empd_vars; +#ifdef __WIN32__ + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(1, 1); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) + epmd_cleanup_exit(g,1); + + if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion ) != 1) { + WSACleanup(); + epmd_cleanup_exit(g,1); + } +#endif +#ifdef DONT_USE_MAIN + if(free_argv) + g->argv = argv; + else + g->argv = NULL; +#else + g->argv = NULL; +#endif + + g->port = get_port_no(); + g->debug = 0; + + g->silent = 0; + g->is_daemon = 0; + g->packet_timeout = CLOSE_TIMEOUT; /* Default timeout */ + g->delay_accept = 0; + g->delay_write = 0; + g->progname = argv[0]; + g->listenfd = -1; + g->conn = NULL; + g->nodes.reg = g->nodes.unreg = g->nodes.unreg_tail = NULL; + g->nodes.unreg_count = 0; + g->active_conn = 0; + + argc--; + argv++; + while (argc > 0) { + if ((strcmp(argv[0], "-debug")==0) || + (strcmp(argv[0], "-d")==0)) { + g->debug += 1; + argv++; argc--; + } else if (strcmp(argv[0], "-packet_timeout") == 0) { + if ((argc == 1) || + ((g->packet_timeout = atoi(argv[1])) == 0)) + usage(g); + argv += 2; argc -= 2; + } else if (strcmp(argv[0], "-delay_accept") == 0) { + if ((argc == 1) || + ((g->delay_accept = atoi(argv[1])) == 0)) + usage(g); + argv += 2; argc -= 2; + } else if (strcmp(argv[0], "-delay_write") == 0) { + if ((argc == 1) || + ((g->delay_write = atoi(argv[1])) == 0)) + usage(g); + argv += 2; argc -= 2; + } else if (strcmp(argv[0], "-daemon") == 0) { + g->is_daemon = 1; + argv++; argc--; + } else if (strcmp(argv[0], "-kill") == 0) { + if (argc == 1) + kill_epmd(g); + else + usage(g); + epmd_cleanup_exit(g,0); + } else if (strcmp(argv[0], "-port") == 0) { + if ((argc == 1) || + ((g->port = atoi(argv[1])) == 0)) + usage(g); + argv += 2; argc -= 2; + } else if (strcmp(argv[0], "-names") == 0) { + if (argc == 1) + epmd_call(g, EPMD_NAMES_REQ); + else + usage(g); + epmd_cleanup_exit(g,0); + } else if (strcmp(argv[0], "-started") == 0) { + g->silent = 1; + if (argc == 1) + epmd_call(g, EPMD_NAMES_REQ); + else + usage(g); + epmd_cleanup_exit(g,0); + } else if (strcmp(argv[0], "-dump") == 0) { + if (argc == 1) + epmd_call(g, EPMD_DUMP_REQ); + else + usage(g); + epmd_cleanup_exit(g,0); + } + else + usage(g); + } + dbg_printf(g,0,"epmd running - daemon = %d",g->is_daemon); + +#ifndef NO_SYSCONF + if ((g->max_conn = sysconf(_SC_OPEN_MAX)) <= 0) +#endif + g->max_conn = MAX_FILES; + + /* + * max_conn must not be greater than FD_SETSIZE. + * (at least QNX crashes) + * + * More correctly, it must be FD_SETSIZE - 1, beacuse the + * listen FD is stored outside the connection array. + */ + + if (g->max_conn > FD_SETSIZE) { + g->max_conn = FD_SETSIZE - 1; + } + + if (g->is_daemon) { + run_daemon(g); + } else { + run(g); + } + return 0; +} + +#ifndef NO_DAEMON +static void run_daemon(EpmdVars *g) +{ + register int child_pid, fd; + + dbg_tty_printf(g,2,"fork a daemon"); + + /* fork to make sure first child is not a process group leader */ + if (( child_pid = fork()) < 0) + { +#ifndef NO_SYSLOG + syslog(LOG_ERR,"erlang mapper daemon cant fork %m"); +#endif + epmd_cleanup_exit(g,1); + } + else if (child_pid > 0) + { + dbg_tty_printf(g,2,"daemon child is %d",child_pid); + epmd_cleanup_exit(g,0); /*parent */ + } + + if (setsid() < 0) + { + dbg_perror(g,"epmd: Cant setsid()"); + epmd_cleanup_exit(g,1); + } + + /* ???? */ + + + signal(SIGHUP, SIG_IGN); + + /* We don't want to be session leader so we fork again */ + + if ((child_pid = fork()) < 0) + { +#ifndef NO_SYSLOG + syslog(LOG_ERR,"erlang mapper daemon cant fork 2'nd time %m"); +#endif + epmd_cleanup_exit(g,1); + } + else if (child_pid > 0) + { + dbg_tty_printf(g,2,"daemon 2'nd child is %d",child_pid); + epmd_cleanup_exit(g,0); /*parent */ + } + + /* move cwd to root to make sure we are not on a mounted filesystem */ + chdir("/"); + + umask(0); + + for (fd = 0; fd < g->max_conn ; fd++) /* close all files ... */ + close(fd); + /* Syslog on linux will try to write to whatever if we dont + inform it of that the log is closed. */ + closelog(); + + /* These chouldn't be needed but for safety... */ + + open("/dev/null", O_RDONLY); /* Order is important! */ + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); + + errno = 0; /* if set by open */ + + run(g); +} + +#endif /* NO_DAEMON */ + +#ifdef __WIN32__ +static int has_console(void) +{ + HANDLE handle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + return 0; + } else { + CloseHandle(handle); + return 1; + } +} + +static void run_daemon(EpmdVars *g) +{ + if (has_console()) { + if (spawnvp(_P_DETACH, __argv[0], __argv) == -1) { + fprintf(stderr, "Failed to spawn detached epmd\n"); + exit(1); + } + exit(0); + } + + close(0); + close(1); + close(2); + + /* These chouldn't be needed but for safety... */ + + open("nul", O_RDONLY); + open("nul", O_WRONLY); + open("nul", O_WRONLY); + + run(g); +} +#endif + +#if (defined(VXWORKS) || defined(_OSE_)) +static void run_daemon(EpmdVars *g) +{ + run(g); +} +#endif + + +/*************************************************************************** + * Misc support routines + * + */ + +static void usage(EpmdVars *g) +{ + fprintf(stderr, "usage: epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon]\n"); + fprintf(stderr, " [-d|-debug] [-port No] [-names|-kill]\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, " -port No\n"); + fprintf(stderr, " Let epmd listen to another port than default %d\n", + EPMD_PORT_NO); + fprintf(stderr, " -d\n"); + fprintf(stderr, " -debug\n"); + fprintf(stderr, " Enable debugging. This will give a log to\n"); + 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, " 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"); + fprintf(stderr, " -delay_accept Seconds\n"); + fprintf(stderr, " To simulate a busy server you can insert a\n"); + fprintf(stderr, " delay between epmd gets notified about that\n"); + fprintf(stderr, " a new connection is requested and when the\n"); + fprintf(stderr, " connections gets accepted.\n\n"); + 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"); + epmd_cleanup_exit(g,1); +} + +/*************************************************************************** + * Error reporting - dbg_printf() & dbg_tty_printf & dbg_perror() + * + * The first form will print out on tty or syslog depending on + * if it runs as deamon or not. The second form will never print + * out on syslog. + * + * The arguments are + * + * g Epmd variables + * from_level From what debug level we print. 0 means always. + * (This argument is missing from dbg_perror() ) + * format Format string + * args... Arguments to print out according to the format + * + */ + +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]; + + if (g->is_daemon) + { +#ifndef NO_SYSLOG + if (onsyslog) + { + vsprintf(buf, format, args); + syslog(LOG_ERR,"epmd: %s",buf); + } +#endif + } + else + { + int len; + + time(&now); + timestr = (char *)ctime(&now); + sprintf(buf, "epmd: %.*s: ", (int) strlen(timestr)-1, timestr); + len = strlen(buf); + vsprintf(buf + len, format, args); + if (perr == 1) + perror(buf); + else + fprintf(stderr,"%s\r\n",buf); + } +} + + +void dbg_perror(EpmdVars *g,const char *format,...) +{ + va_list args; + va_start(args, format); + dbg_gen_printf(1,1,0,g,format,args); + va_end(args); +} + + +void dbg_tty_printf(EpmdVars *g,int from_level,const char *format,...) +{ + if (g->debug >= from_level) { + va_list args; + va_start(args, format); + dbg_gen_printf(0,0,from_level,g,format,args); + va_end(args); + } +} + +void dbg_printf(EpmdVars *g,int from_level,const char *format,...) +{ + if (g->debug >= from_level) { + va_list args; + va_start(args, format); + dbg_gen_printf(1,0,from_level,g,format,args); + va_end(args); + } +} + + +/*************************************************************************** + * + * This function is to clean up all filedescriptors and free up memory on + * VxWorks. + * This function exits, there is nothing else to do when all here is run. + * + */ + +static void free_all_nodes(EpmdVars *g) +{ + Node *tmp; + for(tmp=g->nodes.reg; tmp != NULL; tmp = g->nodes.reg){ + g->nodes.reg = tmp->next; + free(tmp); + } + for(tmp=g->nodes.unreg; tmp != NULL; tmp = g->nodes.unreg){ + g->nodes.unreg = tmp->next; + free(tmp); + } +} +void epmd_cleanup_exit(EpmdVars *g, int exitval) +{ + int i; + + if(g->conn){ + for (i = 0; i < g->max_conn; i++) + if (g->conn[i].open == EPMD_TRUE) + epmd_conn_close(g,&g->conn[i]); + free(g->conn); + } + if(g->listenfd >= 0) + close(g->listenfd); + free_all_nodes(g); + if(g->argv){ + for(i=0; g->argv[i] != NULL; ++i) + free(g->argv[i]); + free(g->argv); + } + + + exit(exitval); +} + +static int get_port_no(void) +{ + char* port_str = getenv("ERL_EPMD_PORT"); + return (port_str != NULL) ? atoi(port_str) : EPMD_PORT_NO; +} + diff --git a/erts/epmd/src/epmd.h b/erts/epmd/src/epmd.h new file mode 100644 index 0000000000..9e939ee38e --- /dev/null +++ b/erts/epmd/src/epmd.h @@ -0,0 +1,37 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* The port number is now defined in a makefile */ + +/* Definitions of message codes */ + +#define EPMD_ALIVE_REQ 'a' +#define EPMD_ALIVE_OK_RESP 'Y' +#define EPMD_PORT_REQ 'p' +#define EPMD_NAMES_REQ 'n' +#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 new file mode 100644 index 0000000000..c12f711bc5 --- /dev/null +++ b/erts/epmd/src/epmd_cli.c @@ -0,0 +1,127 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "epmd.h" /* Renamed from 'epmd_r4.h' */ +#include "epmd_int.h" + +/* forward declarations */ + +static int conn_to_epmd(EpmdVars*); +static int read_fill(int,char*,int); + + +void kill_epmd(EpmdVars *g) +{ + char buf[5]; + int fd, rval; + + fd = conn_to_epmd(g); + put_int16(1,buf); + buf[2] = EPMD_KILL_REQ; + if (write(fd, buf, 3) != 3) { + printf("epmd: Can't write to epmd\n"); + epmd_cleanup_exit(g,1); + } + if ((rval = read_fill(fd,buf,2)) == 2) { + printf("Killed\n"); + epmd_cleanup_exit(g,0); + } else if (rval < 0) { + printf("epmd: failed to read answer from local epmd\n"); + epmd_cleanup_exit(g,1); + } else { /* rval is now 0 or 1 */ + buf[rval] = '\0'; + printf("epmd: local epmd responded with <%s>\n", buf); + epmd_cleanup_exit(g,1); + } +} + +/* what == EPMD_NAMES_REQ || EPMD_DUMP_REQ */ + +void epmd_call(EpmdVars *g,int what) +{ + char buf[OUTBUF_SIZE]; + int rval,fd,i,j; + + fd = conn_to_epmd(g); + put_int16(1,buf); + buf[2] = what; + write(fd,buf,3); + if (read(fd,(char *)&i,4) != 4) { + if (!g->silent) + printf("epmd: no response from local epmd\n"); + epmd_cleanup_exit(g,1); + } + j = ntohl(i); + if (!g->silent) + printf("epmd: up and running on port %d with data:\n", j); + while(1) { + if ((rval = read(fd,buf,1)) <= 0) { + close(fd); + epmd_cleanup_exit(g,0); + } + buf[rval] = '\0'; + if (!g->silent) + printf("%s",buf); + } +} + + + +static int conn_to_epmd(EpmdVars *g) +{ + struct EPMD_SOCKADDR_IN address; + int connect_sock; + + connect_sock = socket(FAMILY, SOCK_STREAM, 0); + if (connect_sock<0) + goto error; + + { /* store port number in unsigned short */ + unsigned short sport = g->port; + SET_ADDR_LOOPBACK(address, FAMILY, sport); + } + + if (connect(connect_sock, (struct sockaddr*)&address, sizeof address) < 0) + goto error; + return connect_sock; + + error: + if (!g->silent) { + fprintf(stderr, "epmd: Cannot connect to local epmd\n"); + } + epmd_cleanup_exit(g,1); + return -1; +} + +/* Fill buffer, return buffer length, 0 for EOF, < 0 for error. */ +static int read_fill(int fd,char *buf,int len) +{ + int i; + int got = 0; + + do { + if ((i = read(fd, buf+got, len-got)) <= 0) + return (i); + got += i; + } while (got < len); + return (len); +} diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h new file mode 100644 index 0000000000..b120b44579 --- /dev/null +++ b/erts/epmd/src/epmd_int.h @@ -0,0 +1,346 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * This file is for internal use within epmd. + */ + +/* This file don't depend on "sys.h" so we have to do some target + definitions ourselves */ + +#ifdef __WIN32__ +#define NO_SYSLOG +#define NO_SYSCONF +#define NO_DAEMON +#endif + +#ifdef VXWORKS +#define NO_SYSLOG +#define NO_SYSCONF +#define NO_DAEMON +#define NO_FCNTL +#define DONT_USE_MAIN +#endif + +#ifdef _OSE_ +#define NO_SYSLOG +#define NO_SYSCONF +#define NO_DAEMON +#define DONT_USE_MAIN +#ifndef HAVE_SYS_TIME_H +#define HAVE_SYS_TIME_H +#endif +#ifndef HAVE_UNISTD_H +#define HAVE_UNISTD_H +#endif +#endif + +/* ************************************************************************ */ +/* Standard includes */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __WIN32__ +# ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +# include <process.h> +#endif + +#include <sys/types.h> +#include <fcntl.h> + +#ifdef VXWORKS +# include <sys/times.h> +# include <time.h> +# include <selectLib.h> +# include <sysLib.h> +# include <sockLib.h> +# include <ioLib.h> +# include <taskLib.h> +# include <rpc/rpc.h> +#else /* ! VXWORKS */ +#ifndef __WIN32__ +# ifdef TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +# else +# ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +# endif +#endif +#endif /* ! VXWORKS */ + +#if (!defined(__WIN32__) && !defined(_OSE_)) +# include <netinet/in.h> +# include <sys/socket.h> +# include <sys/stat.h> + +# ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +# include <rpc/types.h> +# endif + +# include <arpa/inet.h> +# include <netinet/tcp.h> +#endif /* ! WIN32 */ + +#ifndef _OSE_ +#include <ctype.h> +#include <signal.h> +#endif + +#include <errno.h> + +#ifndef NO_SYSLOG +# include <syslog.h> +#endif + +#ifdef SYS_SELECT_H +# include <sys/select.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include <stdarg.h> + +#ifdef _OSE_ +# include "ose.h" +# include "inet.h" +# include "sys/stat.h" +#endif + + +/* ************************************************************************ */ +/* Replace some functions by others by making the function name a macro */ + +#ifdef __WIN32__ +# define close(s) closesocket((s)) +# define write(a,b,c) send((a),(b),(c),0) +# define read(a,b,c) recv((a),(char *)(b),(c),0) +# define sleep(s) Sleep((s) * 1000) +# define ioctl(s,r,o) ioctlsocket((s),(r),(o)) +#endif /* WIN32 */ + +#ifdef VXWORKS +#define sleep(n) taskDelay((n) * sysClkRateGet()) +#endif /* VXWORKS */ + +#ifdef _OSE_ +#define sleep(n) delay((n)) +#endif + +#ifdef USE_BCOPY +# define memcpy(a, b, c) bcopy((b), (a), (c)) +# define memcmp(a, b, c) bcmp((a), (b), (c)) +# define memzero(buf, len) bzero((buf), (len)) +#else +# define memzero(buf, len) memset((buf), '\0', (len)) +#endif + +/* ************************************************************************ */ +/* Try to find replacement values for undefined system parameters */ + +#if defined(__WIN32__) && !defined(EADDRINUSE) +# define EADDRINUSE WSAEADDRINUSE +#endif + +#ifndef SOMAXCONN +# define SOMAXCONN 128 +#endif + +/* + * How to get max no of file descriptors? We used to use NOFILE from + * <sys/param.h>, but that tends to have little relation to reality. + * Best is to use sysconf() (POSIX), but we'll just punt if that isn't + * available. Start out with a high value because it will also be + * used as the number of file descriptors given to select() (it's is + * a terrible bug not to have all file descriptors included in the select()). + * The value will be adjusted down if FD_SETSIZE is smaller. + */ + +#define MAX_FILES 2048 /* if sysconf() isn't available, or fails */ + +/* ************************************************************************ */ +/* Macros that let us use IPv6 */ + +#if defined(HAVE_IN6) && defined(AF_INET6) && defined(EPMD6) + +#define EPMD_SOCKADDR_IN sockaddr_in6 +#define FAMILY AF_INET6 + +#define SET_ADDR_LOOPBACK(addr, af, port) do { \ + static u_int32_t __addr[4] = IN6ADDR_LOOPBACK_INIT; \ + memset((char*)&(addr), 0, sizeof(addr)); \ + (addr).sin6_family = (af); \ + (addr).sin6_flowinfo = 0; \ + (addr).sin6_addr.s6_addr32[0] = __addr[0]; \ + (addr).sin6_addr.s6_addr32[1] = __addr[1]; \ + (addr).sin6_addr.s6_addr32[2] = __addr[2]; \ + (addr).sin6_addr.s6_addr32[3] = __addr[3]; \ + (addr).sin6_port = htons(port); \ + } while(0) + +#define SET_ADDR_ANY(addr, af, port) do { \ + static u_int32_t __addr[4] = IN6ADDR_ANY_INIT; \ + memset((char*)&(addr), 0, sizeof(addr)); \ + (addr).sin6_family = (af); \ + (addr).sin6_flowinfo = 0; \ + (addr).sin6_addr.s6_addr32[0] = __addr[0]; \ + (addr).sin6_addr.s6_addr32[1] = __addr[1]; \ + (addr).sin6_addr.s6_addr32[2] = __addr[2]; \ + (addr).sin6_addr.s6_addr32[3] = __addr[3]; \ + (addr).sin6_port = htons(port); \ + } while(0) + +#else /* Not IP v6 */ + +#define EPMD_SOCKADDR_IN sockaddr_in +#define FAMILY AF_INET + +#define SET_ADDR_LOOPBACK(addr, af, port) do { \ + memset((char*)&(addr), 0, sizeof(addr)); \ + (addr).sin_family = (af); \ + (addr).sin_addr.s_addr = htonl(INADDR_LOOPBACK); \ + (addr).sin_port = htons(port); \ + } while(0) + +#define SET_ADDR_ANY(addr, af, port) do { \ + memset((char*)&(addr), 0, sizeof(addr)); \ + (addr).sin_family = (af); \ + (addr).sin_addr.s_addr = htonl(INADDR_ANY); \ + (addr).sin_port = htons(port); \ + } while(0) + +#endif /* Not IP v6 */ + +/* ************************************************************************ */ +/* Our own definitions */ + +#define EPMD_FALSE 0 +#define EPMD_TRUE 1 + +/* If no activity we let select() return every IDLE_TIMEOUT second + A file descriptor that are idle for CLOSE_TIMEOUT seconds and + isn't a ALIVE socket is probably hanging and we close it */ + +#define IDLE_TIMEOUT 5 +#define CLOSE_TIMEOUT 60 + +/* We save the name of nodes that are unregistered. If a new + node register the name we want to increment the "creation", + a constant 1..3. But we put an limit to this saving to keep + the lookup fast and not to leak memory. */ + +#define MAX_UNREG_COUNT 1000 +#define DEBUG_MAX_UNREG_COUNT 5 + +/* Maximum length of a node name == atom name */ +#define MAXSYMLEN 255 + +#define INBUF_SIZE 1024 +#define OUTBUF_SIZE 1024 + +#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ + (((unsigned char*) (s))[1])) + +#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \ + ((unsigned char*)(s))[1] = (i) & 0xff;} + +/* ************************************************************************ */ + +/* Stuctures used by server */ + +typedef struct { + int fd; /* File descriptor */ + unsigned open:1; /* TRUE if open */ + unsigned keep:1; /* Don't close when sent reply */ + unsigned got; /* # of bytes we have got */ + unsigned want; /* Number of bytes we want */ + char *buf; /* The remaining buffer */ + + time_t mod_time; /* Last activity on this socket */ +} Connection; + +struct enode { + struct enode *next; + int fd; /* The socket in use */ + unsigned short port; /* Port number of Erlang node */ + char symname[MAXSYMLEN+1]; /* Name of the Erlang node */ + short creation; /* Started as a random number 1..3 */ + char nodetype; /* 77 = normal erlang node 72 = hidden (c-node */ + char protocol; /* 0 = tcp/ipv4 */ + unsigned short highvsn; /* 0 = OTP-R3 erts-4.6.x, 1 = OTP-R4 erts-4.7.x*/ + unsigned short lowvsn; + char extra[MAXSYMLEN+1]; +}; + +typedef struct enode Node; + +typedef struct { + Node *reg; + Node *unreg; + Node *unreg_tail; + int unreg_count; +} Nodes; + + +/* This is the structure with all variables needed to pass on + to all functions. This makes this program reentrant */ + +typedef struct { + int port; + int debug; + int silent; + int is_daemon; + unsigned packet_timeout; + unsigned delay_accept; + unsigned delay_write; + int max_conn; + int active_conn; + char *progname; + Connection *conn; + Nodes nodes; + fd_set orig_read_mask; + int listenfd; + char **argv; +} EpmdVars; + +void dbg_printf(EpmdVars*,int,const char*,...); +void dbg_tty_printf(EpmdVars*,int,const char*,...); +void dbg_perror(EpmdVars*,const char*,...); +void kill_epmd(EpmdVars*); +void epmd_call(EpmdVars*,int); +void run(EpmdVars*); +void epmd_cleanup_exit(EpmdVars*, int); +int epmd_conn_close(EpmdVars*,Connection*); + +#ifdef DONT_USE_MAIN +int start_epmd(char *,char *,char *,char *,char *,char *,char *,char *,char *,char *); +int epmd(int,char **); +int epmd_dbg(int,int); +#endif + + diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c new file mode 100644 index 0000000000..b71e27cffd --- /dev/null +++ b/erts/epmd/src/epmd_srv.c @@ -0,0 +1,1254 @@ +/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "epmd.h" /* Renamed from 'epmd_r4.h' */ +#include "epmd_int.h" + +/* + * + * This server is a local name server for Erlang nodes. Erlang nodes can + * ask this server for the listening port of other Erlang nodes on the + * machine EPMD is running on. New distributed nodes that are started + * register their names with this server. + * + * To be accessible to all Erlang nodes this server listens to a well + * known port EPMD_PORT_NO (curently port 4369) where requests + * for connections can be sent. + * + * To keep track of when registered Erlang nodes are terminated this + * server keeps the socket open where the request for registration was + * made. + * + * The protocol is briefly documented in "erl_ext_dist.txt". All requests + * to this server are done with a packet + * + * 2 n + * +--------+---------+ + * | 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 + so that a request will not use a slot with a name that we + want to resuse later incrementing the "creation" */ + + +/* forward declarations */ + +static void do_request(EpmdVars*,int,Connection*,char*,int); +static int do_accept(EpmdVars*,int); +static void do_read(EpmdVars*,Connection*); +static time_t current_time(EpmdVars*); + +static Connection *conn_init(EpmdVars*); +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, char*); +static int node_unreg(EpmdVars*,char*); +static int node_unreg_sock(EpmdVars*,int); + +static int reply(EpmdVars*,int,char *,int); +static void dbg_print_buf(EpmdVars*,char *,int); +static void print_names(EpmdVars*); + + +void run(EpmdVars *g) +{ + int listensock; + int i; + int opt; + struct EPMD_SOCKADDR_IN iserv_addr; + + node_init(g); + g->conn = conn_init(g); + + dbg_printf(g,2,"try to initiate listening port %d", g->port); + + if ((listensock = socket(FAMILY,SOCK_STREAM,0)) < 0) { + dbg_perror(g,"error opening stream socket"); + epmd_cleanup_exit(g,1); + } + g->listenfd = listensock; + + /* + * Initialize number of active file descriptors. + * Stdin, stdout, and stderr are still open. + * One for the listen socket. + */ + g->active_conn = 3+1; + + /* + * Note that we must not enable the SO_REUSEADDR on Windows, + * because addresses will be reused even if they are still in use. + */ + +#if (!defined(__WIN32__) && !defined(_OSE_)) + /* We ignore the SIGPIPE signal that is raised when we call write + twice on a socket closed by the other end. */ + signal(SIGPIPE, SIG_IGN); + + opt = 1; /* Set this option */ + if (setsockopt(listensock,SOL_SOCKET,SO_REUSEADDR,(char* ) &opt, + sizeof(opt)) <0) { + dbg_perror(g,"can't set sockopt"); + epmd_cleanup_exit(g,1); + } +#endif + + /* In rare cases select returns because there is someone + to accept but the request is withdrawn before the + accept function is called. We set the listen socket + to be non blocking to prevent us from being hanging + in accept() waiting for the next request. */ +#ifdef _OSE_ + opt = 1; + if (ioctl(listensock, FIONBIO, (char*)&opt) != 0) +#else +#if (defined(__WIN32__) || defined(NO_FCNTL)) + opt = 1; + if (ioctl(listensock, FIONBIO, &opt) != 0) /* Gives warning in VxWorks */ +#else + opt = fcntl(listensock, F_GETFL, 0); + if (fcntl(listensock, F_SETFL, opt | O_NONBLOCK) == -1) +#endif /* __WIN32__ || VXWORKS */ +#endif /* _OSE_ */ + dbg_perror(g,"failed to set non-blocking mode of listening socket %d", + listensock); + + { /* store port number in unsigned short */ + unsigned short sport = g->port; + SET_ADDR_ANY(iserv_addr, FAMILY, sport); + } + +#ifdef _OSE_ + { + int optlen = sizeof(opt); + opt = 1; + if(getsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, + (void*)&opt, &optlen) < 0) + fprintf(stderr, "\n\nGETSOCKOPT FAILS! %d\n\n", errno); + else if(opt == 1) + fprintf(stderr, "SO_REUSEADDR is set!\n"); + } +#endif + + if(bind(listensock,(struct sockaddr*) &iserv_addr, sizeof(iserv_addr)) < 0 ) + { + if (errno == EADDRINUSE) + { + dbg_tty_printf(g,1,"there is already a epmd running at port %d", + g->port); + epmd_cleanup_exit(g,0); + } + else + { + dbg_perror(g,"failed to bind socket"); + epmd_cleanup_exit(g,1); + } + } + + dbg_printf(g,2,"starting"); + + listen(listensock, SOMAXCONN); + + + FD_ZERO(&g->orig_read_mask); + FD_SET(listensock,&g->orig_read_mask); + + dbg_tty_printf(g,2,"entering the main select() loop"); + + select_again: + while(1) + { + fd_set read_mask = g->orig_read_mask; + struct timeval timeout; + int ret; + + /* If we are idle we time out now and then to enable the code + below to close connections that are old and probably + hanging. Make sure that select will return often enough. */ + + timeout.tv_sec = (g->packet_timeout < IDLE_TIMEOUT) ? 1 : IDLE_TIMEOUT; + timeout.tv_usec = 0; + + if ((ret = select(g->max_conn,&read_mask,(fd_set *)0,(fd_set *)0,&timeout)) < 0) + dbg_perror(g,"error in select "); + else { + time_t now; + if (ret == 0) { + FD_ZERO(&read_mask); + } + if (g->delay_accept) { /* Test of busy server */ + sleep(g->delay_accept); + } + + if (FD_ISSET(listensock,&read_mask)) { + if (do_accept(g, listensock) && g->active_conn < g->max_conn) { + /* + * The accept() succeeded, and we have at least one file + * descriptor still free, which means that another accept() + * could succeed. Go do do another select(), in case there + * are more incoming connections waiting to be accepted. + */ + goto select_again; + } + } + + /* Check all open streams marked by select for data or a + close. We also close all open sockets except ALIVE + with no activity for a long period */ + + now = current_time(g); + for (i = 0; i < g->max_conn; i++) { + if (g->conn[i].open == EPMD_TRUE) { + if (FD_ISSET(g->conn[i].fd,&read_mask)) + do_read(g,&g->conn[i]); + else if ((g->conn[i].keep == EPMD_FALSE) && + ((g->conn[i].mod_time + g->packet_timeout) < now)) { + dbg_tty_printf(g,1,"closing because timed out on receive"); + epmd_conn_close(g,&g->conn[i]); + } + } + } + } + } +} + +/* + * This routine read as much of the packet as possible and + * if completed calls "do_request()" to fullfill the request. + * + */ + +static void do_read(EpmdVars *g,Connection *s) +{ + int val, pack_size; + + if (s->open == EPMD_FALSE) + { + dbg_printf(g,0,"read on unknown socket"); + return; + } + + /* Check if we already got the whole packet but we keep the + connection alive to find out when a node is terminated. We then + want to check for a close */ + + if (s->keep == EPMD_TRUE) + { + val = read(s->fd, s->buf, INBUF_SIZE); + + if (val == 0) + { + node_unreg_sock(g,s->fd); + epmd_conn_close(g,s); + } + else if (val < 0) + { + dbg_tty_printf(g,1,"error on ALIVE socket %d (%d; errno=0x%x)", + s->fd, val, errno); + node_unreg_sock(g,s->fd); + epmd_conn_close(g,s); + } + else + { + dbg_tty_printf(g,1,"got more than expected on ALIVE socket %d (%d)", + 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; + } + + /* If unknown size we request the whole buffer - what we got - 1 + We subtract 1 because we will add a "\0" in "do_request()". + This is not needed for R3A or higher versions of Erlang, + because the '\0' is included in the request, + but is kept for backwards compatibility to allow R2D to use + this epmd. */ + + pack_size = s->want ? s->want : INBUF_SIZE - 1; + val = read(s->fd, s->buf + s->got, pack_size - s->got); + + if (val == 0) + { + /* A close when we haven't got all data */ + dbg_printf(g,0,"got partial packet only on file descriptor %d (%d)", + s->fd,s->got); + epmd_conn_close(g,s); + return; + } + + if (val < 0) + { + dbg_perror(g,"error in read"); + epmd_conn_close(g,s); + return; + } + + dbg_print_buf(g,s->buf,val); + + s->got += val; + + if ((s->want == 0) && (s->got >= 2)) + { + /* The two byte header that specify the length of the packet + doesn't count the header as part of the packet so we add 2 + to "s->want" to make us talk about all bytes we get. */ + + s->want = get_int16(s->buf) + 2; + + if ((s->want < 3) || (s->want >= INBUF_SIZE)) + { + dbg_printf(g,0,"invalid packet size (%d)",s->want - 2); + epmd_conn_close(g,s); + return; + } + + if (s->got > s->want) + { + dbg_printf(g,0,"got %d bytes in packet, expected %d", + s->got - 2, s->want - 2); + epmd_conn_close(g,s); + return; + } + } + + s->mod_time = current_time(g); /* Note activity */ + + if (s->want == s->got) + { + /* Do action and close up */ + /* Skip header bytes */ + + do_request(g, s->fd, s, s->buf + 2, s->got - 2); + + if (!s->keep) + epmd_conn_close(g,s); /* Normal close */ + } +} + +static int do_accept(EpmdVars *g,int listensock) +{ + int msgsock; + struct EPMD_SOCKADDR_IN icli_addr; /* workaround for QNX bug - cannot */ + int icli_addr_len; /* handle NULL pointers to accept. */ + + icli_addr_len = sizeof(icli_addr); + + msgsock = accept(listensock,(struct sockaddr*) &icli_addr, + (unsigned int*) &icli_addr_len); + + if (msgsock < 0) { + dbg_perror(g,"error in accept"); + return EPMD_FALSE; + } + + return conn_open(g,msgsock); +} + +static void do_request(g, fd, s, buf, bsize) + EpmdVars *g; + int fd; + Connection *s; + char *buf; + int 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'; + + 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"); + + /* 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) + { + dbg_printf(g,0,"packet to small for request ALIVE2_REQ (%d)",bsize); + return; + } + + { + Node *node; + int eport; + unsigned char nodetype; + unsigned char protocol; + unsigned short highvsn; + unsigned short lowvsn; + int namelen; + int extralen; + char *name; + char *extra; + eport = get_int16(&buf[1]); + nodetype = buf[3]; + protocol = buf[4]; + highvsn = get_int16(&buf[5]); + lowvsn = get_int16(&buf[7]); + namelen = get_int16(&buf[9]); + extralen = get_int16(&buf[11+namelen]); + for (i = 11 ; i < 11 + namelen; i ++) + if (buf[i] == '\000') { + dbg_printf(g,0,"node name contains ascii 0 in ALIVE2_REQ"); + return; + } + name = &buf[11]; + name[namelen]='\000'; + extra = &buf[11+namelen+1]; + extra[extralen]='\000'; + wbuf[0] = EPMD_ALIVE2_RESP; + if ((node = node_reg2(g, name, fd, eport, nodetype, protocol, + highvsn, lowvsn, extra)) == NULL) { + wbuf[1] = 1; /* error */ + put_int16(99, wbuf+2); + } else { + wbuf[1] = 0; /* ok */ + put_int16(node->creation, wbuf+2); + } + + if (g->delay_write) /* Test of busy server */ + sleep(g->delay_write); + + if (reply(g, fd, wbuf, 4) != 4) + { + dbg_tty_printf(g,1,"** failed to send ALIVE2_RESP for \"%s\"", + name); + return; + } + + dbg_tty_printf(g,1,"** sent ALIVE2_RESP for \"%s\"",name); + s->keep = EPMD_TRUE; /* Don't close on inactivity */ + } + break; + + case EPMD_PORT2_REQ: + dbg_printf(g,1,"** got PORT2_REQ"); + + if (buf[bsize - 1] == '\000') /* Skip null termination */ + bsize--; + + if (bsize <= 1) + { + dbg_printf(g,0,"packet to small for request PORT2_REQ (%d)", bsize); + return; + } + + for (i = 1; i < bsize; i++) + if (buf[i] == '\000') + { + dbg_printf(g,0,"node name contains ascii 0 in PORT2_REQ"); + return; + } + + { + char *name = &buf[1]; /* Points to node name */ + Node *node; + + wbuf[0] = EPMD_PORT2_RESP; + for (node = g->nodes.reg; node; node = node->next) { + int offset; + if (strcmp(node->symname, name) == 0) { + wbuf[1] = 0; /* ok */ + put_int16(node->port,wbuf+2); + wbuf[4] = node->nodetype; + wbuf[5] = node->protocol; + put_int16(node->highvsn,wbuf+6); + put_int16(node->lowvsn,wbuf+8); + put_int16(strlen(node->symname),wbuf+10); + offset = 12; + strcpy(wbuf + offset,node->symname); + offset += strlen(node->symname); + put_int16(strlen(node->extra),wbuf + offset); + offset += 2; + strcpy(wbuf + offset,node->extra); + offset += (strlen(node->extra)-1); + if (reply(g, fd, wbuf, offset) != offset) + { + dbg_tty_printf(g,1,"** failed to send PORT2_RESP (ok) for \"%s\"",name); + return; + } + dbg_tty_printf(g,1,"** sent PORT2_RESP (ok) for \"%s\"",name); + return; + } + } + wbuf[1] = 1; /* error */ + if (reply(g, fd, wbuf, 2) != 2) + { + dbg_tty_printf(g,1,"** failed to send PORT2_RESP (error) for \"%s\"",name); + return; + } + dbg_tty_printf(g,1,"** sent PORT2_RESP (error) for \"%s\"",name); + return; + } + break; + + case EPMD_NAMES_REQ: + dbg_printf(g,1,"** got NAMES_REQ"); + { + Node *node; + + i = htonl(g->port); + memcpy(wbuf,&i,4); + + if (reply(g, fd,wbuf,4) != 4) + { + dbg_tty_printf(g,1,"failed to send NAMES_RESP"); + return; + } + + for (node = g->nodes.reg; node; node = node->next) + { + int len; + + /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight + change in syntax will break < OTP R3A */ + + sprintf(wbuf,"name %s at port %d\n",node->symname, node->port); + len = strlen(wbuf); + if (reply(g, fd, wbuf, len) != len) + { + dbg_tty_printf(g,1,"failed to send NAMES_RESP"); + return; + } + } + } + dbg_tty_printf(g,1,"** sent NAMES_RESP"); + break; + + case EPMD_DUMP_REQ: + dbg_printf(g,1,"** got DUMP_REQ"); + { + Node *node; + + i = htonl(g->port); + memcpy(wbuf,&i,4); + if (reply(g, fd,wbuf,4) != 4) + { + dbg_tty_printf(g,1,"failed to send DUMP_RESP"); + return; + } + + for (node = g->nodes.reg; node; node = node->next) + { + int len; + + /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight + change in syntax will break < OTP R3A */ + + sprintf(wbuf,"active name <%s> at port %d, fd = %d\n", + node->symname, node->port, node->fd); + len = strlen(wbuf) + 1; + if (reply(g, fd,wbuf,len) != len) + { + dbg_tty_printf(g,1,"failed to send DUMP_RESP"); + return; + } + } + + for (node = g->nodes.unreg; node; node = node->next) + { + int len; + + /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight + change in syntax will break < OTP R3A */ + + sprintf(wbuf,"old/unused name <%s>, port = %d, fd = %d \n", + node->symname,node->port, node->fd); + len = strlen(wbuf) + 1; + if (reply(g, fd,wbuf,len) != len) + { + dbg_tty_printf(g,1,"failed to send DUMP_RESP"); + return; + } + } + } + dbg_tty_printf(g,1,"** sent DUMP_RESP"); + break; + + case EPMD_KILL_REQ: + dbg_printf(g,1,"** got KILL_REQ"); + 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"); + conn_close_fd(g,fd); /* We never return to caller so close here */ + dbg_printf(g,0,"got KILL_REQ - terminates normal"); + epmd_cleanup_exit(g,0); /* Normal exit */ + + case EPMD_STOP_REQ: + dbg_printf(g,1,"** got STOP_REQ"); + if (bsize <= 1 ) + { + dbg_printf(g,0,"packet to small for request STOP_REQ (%d)",bsize); + return; + } + + { + char *name = &buf[1]; /* Points to node name */ + int node_fd; + + if ((node_fd = node_unreg(g,name)) < 0) + { + if (reply(g, fd,"NOEXIST",7) != 7) + { + dbg_tty_printf(g,1,"failed to send STOP_RESP NOEXIST"); + return; + } + dbg_tty_printf(g,1,"** sent STOP_RESP NOEXIST"); + } + + conn_close_fd(g,node_fd); + dbg_tty_printf(g,1,"epmd connection stopped"); + + if (reply(g, fd,"STOPPED",7) != 7) + { + dbg_tty_printf(g,1,"failed to send STOP_RESP STOPPED"); + return; + } + dbg_tty_printf(g,1,"** sent STOP_RESP STOPPED"); + } + break; + + default: + dbg_printf(g,0,"got garbage "); + } +} + + +/**************************************************************************** + * + * Handle database with data for each socket to read + * + ****************************************************************************/ + +static Connection *conn_init(EpmdVars *g) +{ + int nbytes = g->max_conn * sizeof(Connection); + Connection *connections = (Connection *)malloc(nbytes); + + if (connections == NULL) + { + dbg_printf(g,0,"epmd: Insufficient memory"); +#ifdef DONT_USE_MAIN + free(g->argv); +#endif + exit(1); + } + + memzero(connections, nbytes); + + return connections; +} + +static int conn_open(EpmdVars *g,int fd) +{ + int i; + Connection *s; + +#ifdef VXWORKS + /* + * Since file descriptors are global on VxWorks, we might get an fd that + * does not fit in the FD_SET. + * + * Note: This test would be harmless on Unix, but would fail on Windows + * because socket are numbered differently and FD_SETs are implemented + * differently. + */ + if (fd >= FD_SETSIZE) { + dbg_tty_printf(g,0,"file descriptor %d: too high for FD_SETSIZE=%d", + fd,FD_SETSIZE); + close(fd); + return EPMD_FALSE; + } +#endif + + for (i = 0; i < g->max_conn; i++) { + if (g->conn[i].open == EPMD_FALSE) { + g->active_conn++; + s = &g->conn[i]; + + /* From now on we want to know if there are data to be read */ + FD_SET(fd, &g->orig_read_mask); + + s->fd = fd; + s->open = EPMD_TRUE; + s->keep = EPMD_FALSE; + s->want = 0; /* Currently unknown */ + s->got = 0; + s->mod_time = current_time(g); /* Note activity */ + + s->buf = (char *)malloc(INBUF_SIZE); + + if (s->buf == NULL) { + dbg_printf(g,0,"epmd: Insufficient memory"); + close(fd); + return EPMD_FALSE; + } + + dbg_tty_printf(g,2,"opening connection on file descriptor %d",fd); + return EPMD_TRUE; + } + } + + dbg_tty_printf(g,0,"failed opening connection on file descriptor %d",fd); + close(fd); + return EPMD_FALSE; +} + +static int conn_close_fd(EpmdVars *g,int fd) +{ + int i; + + for (i = 0; i < g->max_conn; i++) + if (g->conn[i].fd == fd) + { + epmd_conn_close(g,&g->conn[i]); + return EPMD_TRUE; + } + + return EPMD_FALSE; +} + + +int epmd_conn_close(EpmdVars *g,Connection *s) +{ + dbg_tty_printf(g,2,"closing connection on file descriptor %d",s->fd); + + FD_CLR(s->fd,&g->orig_read_mask); + close(s->fd); /* Sometimes already closed but close anyway */ + s->open = EPMD_FALSE; + if (s->buf != NULL) { /* Should never be NULL but test anyway */ + free(s->buf); + } + g->active_conn--; + return EPMD_TRUE; +} + +/**************************************************************************** + * + * Handle database with data for each registered node + * + ****************************************************************************/ + + +static void node_init(EpmdVars *g) +{ + g->nodes.reg = NULL; + g->nodes.unreg = NULL; + g->nodes.unreg_tail = NULL; + g->nodes.unreg_count = 0; +} + + +/* We have got a close on a connection and it may be a + EPMD_ALIVE_CLOSE_REQ. Note that this call shouild be called + *before* calling conn_close() */ + +static int node_unreg(EpmdVars *g,char *name) +{ + Node **prev = &g->nodes.reg; /* Point to cell pointing to... */ + Node *node = g->nodes.reg; /* Point to first node */ + + for (; node; prev = &node->next, node = node->next) + if (strcmp(node->symname, name) == 0) + { + dbg_tty_printf(g,1,"unregistering '%s:%d', port %d", + node->symname, node->creation, node->port); + + *prev = node->next; /* Link out from "reg" list */ + + if (g->nodes.unreg == NULL) /* Link into "unreg" list */ + g->nodes.unreg = g->nodes.unreg_tail = node; + else + { + g->nodes.unreg_tail->next = node; + g->nodes.unreg_tail = node; + } + + g->nodes.unreg_count++; + + node->next = NULL; /* Last in list == first in FIFO queue */ + + print_names(g); + + return node->fd; + } + + dbg_tty_printf(g,1,"trying to unregister node with unknown name %s", name); + return -1; +} + + +static int node_unreg_sock(EpmdVars *g,int fd) +{ + Node **prev = &g->nodes.reg; /* Point to cell pointing to... */ + Node *node = g->nodes.reg; /* Point to first node */ + + for (; node; prev = &node->next, node = node->next) + if (node->fd == fd) + { + dbg_tty_printf(g,1,"unregistering '%s:%d', port %d", + node->symname, node->creation, node->port); + + *prev = node->next; /* Link out from "reg" list */ + + if (g->nodes.unreg == NULL) /* Link into "unreg" list */ + g->nodes.unreg = g->nodes.unreg_tail = node; + else + { + g->nodes.unreg_tail->next = node; + g->nodes.unreg_tail = node; + } + + g->nodes.unreg_count++; + + node->next = NULL; /* Last in list == first in FIFO queue */ + + print_names(g); + + return node->fd; + } + + dbg_tty_printf(g,1, + "trying to unregister node with unknown file descriptor %d", + fd); + return -1; +} + +/* + * Finding a node slot and a (name,creation) name is a bit tricky. + * We try in order + * + * 1. If the name was used before and we can reuse that slot but use + * a new "creation" digit in the range 1..3. + * + * 2. We try to find a new unused slot. + * + * 3. We try to use an used slot this isn't used any longer. + * FIXME: The criteria for *what* slot to steal should be improved. + * 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, NULL); +} + +static Node *node_reg2(EpmdVars *g, + char* name, + int fd, + int port, + unsigned char nodetype, + unsigned char protocol, + int highvsn, + int lowvsn, + char* extra) +{ + Node *prev; /* Point to previous node or NULL */ + Node *node; /* Point to first node */ + + /* Can be NULL; means old style */ + if (extra == NULL) + extra = ""; + + /* Fail if node name is too long */ + + if (strlen(name) > MAXSYMLEN) + { + dbg_printf(g,0,"node name is too long (%d) %s", strlen(name), name); + return NULL; + } + + /* Fail if it is already registered */ + + for (node = g->nodes.reg; node; node = node->next) + if (strcmp(node->symname, name) == 0) + { + dbg_printf(g,0,"node name already occupied %s", name); + return NULL; + } + + /* Try to find the name in the used queue so that we + can change "creation" number 1..3 */ + + prev = NULL; + + for (node = g->nodes.unreg; node; prev = node, node = node->next) + if (strcmp(node->symname, name) == 0) + { + dbg_tty_printf(g,1,"reusing slot with same name '%s'", node->symname); + + if (prev == NULL) /* First in list matched */ + { + if (node->next == NULL) /* Only one element */ + g->nodes.unreg = g->nodes.unreg_tail = NULL; + else + g->nodes.unreg = node->next; + } + else + { + if (node->next == NULL) /* Last in list */ + { + g->nodes.unreg_tail = prev; /* Point to new last */ + prev->next = NULL; /* New last has no next */ + } + else + prev->next = node->next; /* Simple link out from list */ + } + + g->nodes.unreg_count--; + + /* When reusing we change the "creation" number 1..3 */ + + node->creation = node->creation % 3 + 1; + + break; + } + + if (node == NULL) + { + /* A new name. If the "unreg" list is too long we steal the + oldest node structure and use it for the new node, else + we allocate a new node structure */ + + if ((g->nodes.unreg_count > MAX_UNREG_COUNT) || + (g->debug && (g->nodes.unreg_count > DEBUG_MAX_UNREG_COUNT))) + { + /* MAX_UNREG_COUNT > 1 so no need to check unreg_tail */ + node = g->nodes.unreg; /* Take first == oldest */ + g->nodes.unreg = node->next; /* Link out */ + g->nodes.unreg_count--; + } + else + { + if ((node = (Node *)malloc(sizeof(Node))) == NULL) + { + dbg_printf(g,0,"epmd: Insufficient memory"); + exit(1); + } + + node->creation = (current_time(g) % 3) + 1; /* "random" 1-3 */ + } + } + + node->next = g->nodes.reg; /* Link into "reg" queue */ + g->nodes.reg = node; + + node->fd = fd; + node->port = port; + node->nodetype = nodetype; + node->protocol = protocol; + node->highvsn = highvsn; + node->lowvsn = lowvsn; + strcpy(node->extra,extra); + strcpy(node->symname,name); + FD_SET(fd,&g->orig_read_mask); + + if (highvsn == 0) { + dbg_tty_printf(g,1,"registering '%s:%d', port %d", + node->symname, node->creation, node->port); + } else { + dbg_tty_printf(g,1,"registering '%s:%d', port %d", + node->symname, node->creation, node->port); + dbg_tty_printf(g,1,"type %d proto %d highvsn %d lowvsn %d", + nodetype, protocol, highvsn, lowvsn); + } + + print_names(g); + + return node; +} + + +static time_t current_time(EpmdVars *g) +{ + time_t t = time((time_t *)0); + dbg_printf(g,3,"time in seconds: %d",t); + return t; +} + + +static int reply(EpmdVars *g,int fd,char *buf,int len) +{ + int val; + + if (len < 0) + { + dbg_printf(g,0,"Invalid length in write %d",len); + return -1; + } + + if (g->delay_write) /* Test of busy server */ + sleep(g->delay_write); + + val = write(fd,buf,len); + if (val < 0) + dbg_perror(g,"error in write"); + else if (val != len) + dbg_printf(g,0,"could only send %d bytes out of %d to fd %d",val,len,fd); + + dbg_print_buf(g,buf,len); + + return val; +} + + +#define LINEBYTECOUNT 16 + +static void print_buf_hex(unsigned char *buf,int len,char *prefix) +{ + int rows, row; + + rows = len / LINEBYTECOUNT; /* Number of rows */ + if (len % LINEBYTECOUNT) + rows++; /* If leftovers, add a line for them */ + + for (row = 0; row < rows; row++) + { + int rowstart = row * LINEBYTECOUNT; + int rowend = rowstart + LINEBYTECOUNT; + int i; + + fprintf(stderr,"%s%.8x",prefix,rowstart); + + for (i = rowstart; i < rowend; i++) + { + if ((i % (LINEBYTECOUNT/2)) == 0) + fprintf(stderr," "); + + if (i < len) + fprintf(stderr," %.2x",buf[i]); + else + fprintf(stderr," "); + } + + fprintf(stderr," |"); + + for (i = rowstart; (i < rowend) && (i < len); i++) + { + int c = buf[i]; + + /* Used to be isprint(c) but we want ascii only */ + + if ((c >= 32) && (c <= 126)) + fprintf(stderr,"%c",c); + else + fprintf(stderr,"."); + } + + fprintf(stderr,"|\r\n"); + } +} + +static void dbg_print_buf(EpmdVars *g,char *buf,int len) +{ + int plen; + + if ((g->is_daemon) || /* Don't want to write to stderr if daemon */ + (g->debug < 2)) /* or debug level too low */ + return; + + dbg_tty_printf(g,1,"got %d bytes",len); + + plen = len > 1024 ? 1024 : len; /* Limit the number of chars to print */ + + print_buf_hex((unsigned char*)buf,plen,"***** "); + + if (len != plen) + fprintf(stderr,"***** ......and more\r\n"); +} + +static void print_names(EpmdVars *g) +{ + int count = 0; + Node *node; + + if ((g->is_daemon) || /* Don't want to write to stderr if daemon */ + (g->debug < 3)) /* or debug level too low */ + return; + + for (node = g->nodes.reg; node; node = node->next) + { + fprintf(stderr,"***** active name \"%s#%d\" at port %d, fd = %d\r\n", + node->symname, node->creation, node->port, node->fd); + count ++; + } + + fprintf(stderr, "***** reg calculated count : %d\r\n", count); + + count = 0; + + for (node = g->nodes.unreg; node; node = node->next) + { + fprintf(stderr,"***** old/unused name \"%s#%d\"\r\n", + node->symname, node->creation); + count ++; + } + + fprintf(stderr, "***** unreg counter : %d\r\n", + g->nodes.unreg_count); + fprintf(stderr, "***** unreg calculated count: %d\r\n", count); +} diff --git a/erts/epmd/test/Makefile b/erts/epmd/test/Makefile new file mode 100644 index 0000000000..c1d62f0f93 --- /dev/null +++ b/erts/epmd/test/Makefile @@ -0,0 +1,80 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +include ../epmd.mk + +EBIN = . + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= epmd_SUITE + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELEPMDDIR = $(RELEASE_PATH)/epmd_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- + +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \ + -I$(ERL_TOP)/lib/kernel/src/ \ + $(EPMD_FLAGS) + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +tests debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: + +release_tests_spec: opt + $(INSTALL_DIR) $(RELEPMDDIR) + $(INSTALL_DATA) epmd.spec epmd.spec.vxworks $(ERL_FILES) \ + $(TARGET_FILES) $(RELEPMDDIR) + chmod -f -R u+w $(RELEPMDDIR) + +release_docs_spec: + + + + + diff --git a/erts/epmd/test/epmd.spec b/erts/epmd/test/epmd.spec new file mode 100644 index 0000000000..0e2496bc72 --- /dev/null +++ b/erts/epmd/test/epmd.spec @@ -0,0 +1 @@ +{topcase, {dir, "../epmd_test"}}. diff --git a/erts/epmd/test/epmd.spec.vxworks b/erts/epmd/test/epmd.spec.vxworks new file mode 100644 index 0000000000..476308b481 --- /dev/null +++ b/erts/epmd/test/epmd.spec.vxworks @@ -0,0 +1,2 @@ +{topcase, {dir, "../epmd_test"}}. +{skip,{epmd_rx_SUITE,"EPMD RX does simply not work on VxWorks"}}. diff --git a/erts/epmd/test/epmd_SUITE.erl b/erts/epmd/test/epmd_SUITE.erl new file mode 100644 index 0000000000..513c87a13e --- /dev/null +++ b/erts/epmd/test/epmd_SUITE.erl @@ -0,0 +1,835 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(epmd_SUITE). +-include("test_server.hrl"). +-include_lib("kernel/include/file.hrl"). + + +% Timeout for test cases (rather long to work on slow machines) +-define(SHORT_TEST_TIMEOUT, ?t:seconds(30)). % Default +-define(MEDIUM_TEST_TIMEOUT, ?t:minutes(3)). +-define(LONG_TEST_TIMEOUT, ?t:minutes(10)). + +% Delay inserted into code +-define(SHORT_PAUSE, 100). +-define(MEDIUM_PAUSE, ?t:seconds(1)). +-define(LONG_PAUSE, ?t:seconds(5)). + +% Test server specific exports +-export([all/1, init_per_testcase/2, fin_per_testcase/2]). + +-export( + [ + register_name/1, + register_names_1/1, + register_names_2/1, + register_duplicate_name/1, + get_port_nr/1, + slow_get_port_nr/1, + unregister_others_name_1/1, + unregister_others_name_2/1, + register_overflow/1, + name_with_null_inside/1, + name_null_terminated/1, + stupid_names_req/1, + + no_data/1, + one_byte/1, + two_bytes/1, + partial_packet/1, + zero_length/1, + too_large/1, + alive_req_too_small_1/1, + alive_req_too_small_2/1, + alive_req_too_large/1 + ]). + + +% Port we use for testing +-define(PORT,2243). +-define(EPMDARGS,"-packet_timeout 1"). + +-define(DUMMY_PORT, 1000). % Port number to register + % not in real use. + +% Timeouts etc inside test cases. Time is in milliseconds. +-define(CONN_RETRY, 4). % Times to retry connecting +-define(CONN_SLEEP, 500). +-define(CONN_TIMEOUT, 100). +-define(RECV_TIMEOUT, 2000). +-define(REG_REPEAT_LIM,1000). + +% Message codes in epmd protocol +-define(EPMD_ALIVE_REQ, $a). +-define(EPMD_ALIVE_OK_RESP, $Y). +-define(EPMD_PORT_REQ, $p). +-define(EPMD_NAMES_REQ, $n). +-define(EPMD_DUMP_REQ, $d). +-define(EPMD_KILL_REQ, $k). +-define(EPMD_STOP_REQ, $s). + +%% +%% all/1 +%% + +all(suite) -> + [ + register_name, + register_names_1, + register_names_2, + register_duplicate_name, + get_port_nr, + slow_get_port_nr, + unregister_others_name_1, + unregister_others_name_2, + register_overflow, + name_with_null_inside, + name_null_terminated, + stupid_names_req, + + no_data, + one_byte, + two_bytes, + partial_packet, + zero_length, + too_large, + alive_req_too_small_1, + alive_req_too_small_2, + alive_req_too_large + ]. + +%% +%% Run before and after each test case +%% + +init_per_testcase(_Func, Config) -> + Dog = test_server:timetrap(?SHORT_TEST_TIMEOUT), + cleanup(), + [{watchdog, Dog} | Config]. + +fin_per_testcase(_Func, Config) -> + cleanup(), + Dog = ?config(watchdog, Config), + catch test_server:timetrap_cancel(Dog), % We may have canceled already + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +register_name(doc) -> + ["Register a name"]; +register_name(suite) -> + []; +register_name(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = register_node("foobar"), + ?line ok = close(Sock), % Unregister + ok. + +register_names_1(doc) -> + ["Register and unregister two nodes"]; +register_names_1(suite) -> + []; +register_names_1(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock1} = register_node("foobar"), + ?line {ok,Sock2} = register_node("foozap"), + ?line ok = close(Sock1), % Unregister + ?line ok = close(Sock2), % Unregister + ok. + +register_names_2(doc) -> + ["Register and unregister two nodes"]; +register_names_2(suite) -> + []; +register_names_2(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock1} = register_node("foobar"), + ?line {ok,Sock2} = register_node("foozap"), + ?line ok = close(Sock2), % Unregister + ?line ok = close(Sock1), % Unregister + ok. + +register_duplicate_name(doc) -> + ["Two nodes with the same name"]; +register_duplicate_name(suite) -> + []; +register_duplicate_name(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = register_node("foobar"), + ?line error = register_node("foobar"), + ?line ok = close(Sock), % Unregister + ok. + +% 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 connect() of + {ok,Sock} -> + M = [?EPMD_ALIVE_REQ, put16(Port), Name], + case send(Sock, [size16(M), M]) of + ok -> + 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; + Other -> + test_server:format("send on sock ~w: ~w~n",[Sock,Other]), + error + end; + Other -> + test_server:format("Connect on port ~w: ~p~n",[Port,Other]), + error + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +name_with_null_inside(doc) -> + ["Register a name with a null char in it"]; +name_with_null_inside(suite) -> + []; +name_with_null_inside(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line error = register_node("foo\000bar"), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +name_null_terminated(doc) -> + ["Register a name with terminating null byte"]; +name_null_terminated(suite) -> + []; +name_null_terminated(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = register_node("foobar\000"), + ?line error = register_node("foobar"), + ?line ok = close(Sock), % Unregister + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +stupid_names_req(doc) -> + ["Read names from epmd in a stupid way"]; +stupid_names_req(suite) -> + []; +stupid_names_req(Config) when list(Config) -> + Dog = ?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + LongDog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), + ?line ok = epmdrun(), + ?line [FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"), + ?line unregister_many([FirstConn]), + sleep(?MEDIUM_PAUSE), + ?line ok = check_names(Conn), + ?line ok = unregister_many(Conn), + test_server:timetrap_cancel(LongDog), + ok. + +check_names(Conn) -> + ?line {ok,Sock} = connect_active(), + ?line {ok,Reply} = do_get_names(Sock), + ?line SortConn = lists:sort(Conn), + ?line SortReply = lists:sort(Reply), + ?line ok = check_names_cmp(SortConn, SortReply), + ok. + + +% Compare if the result was the same as was registered + +check_names_cmp([], []) -> + ok; +check_names_cmp([{Name,Port,_Sock} | Conn], [{Name,Port} | Reply]) -> + check_names_cmp(Conn, Reply). + + +% This code is taken directly from "erl_epmd.erl" in R3A01 + +-define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]). +-define(u32(X1,X2,X3,X4), + (((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)). + +do_get_names(Socket) -> + inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES_REQ]), + receive + {tcp, Socket, [P0,P1,P2,P3 | T]} -> + EpmdPort = ?u32(P0,P1,P2,P3), + if EpmdPort == ?PORT -> + names_loop(Socket, T, []); + true -> + close(Socket), + {error, address} + end; + {tcp_closed, Socket} -> + {ok, []} + end. + +names_loop(Socket, Acc, Ps) -> + receive + {tcp, Socket, Bytes} -> + {NAcc, NPs} = scan_names(Acc ++ Bytes, Ps), + names_loop(Socket, NAcc, NPs); + {tcp_closed, Socket} -> + {_, NPs} = scan_names(Acc, Ps), % Really needed? + {ok, NPs} + end. + +scan_names(Buf, Ps) -> + case scan_line(Buf, []) of + {Line, NBuf} -> + case parse_line(Line) of + {ok, Entry} -> + scan_names(NBuf, [Entry | Ps]); + error -> + scan_names(NBuf, Ps) + end; + [] -> {Buf, Ps} + end. + +scan_line([$\n | Buf], Line) -> {lists:reverse(Line), Buf}; +scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]); +scan_line([], _) -> []. + +parse_line([$n,$a,$m,$e,$ | Buf0]) -> + case parse_name(Buf0, []) of + {Name, Buf1} -> + case Buf1 of + [$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] -> + case catch list_to_integer(Buf2) of + {'EXIT', _} -> error; + Port -> {ok, {Name, Port}} + end; + _ -> error + end; + error -> error + end; +parse_line(_) -> error. + + +parse_name([$ | Buf], Name) -> {lists:reverse(Name), Buf}; +parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]); +parse_name([], _Name) -> error. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_port_nr(doc) -> + ["Register a name on a port and ask about port nr"]; +get_port_nr(suite) -> + []; +get_port_nr(Config) when list(Config) -> + port_request([?EPMD_PORT_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]). + + +% Internal function used above + +port_request(M) -> + ?line ok = epmdrun(), + Port = 1042, + ?line {ok,RSock} = register_node("foo", Port), + ?line {ok,Sock} = connect(), + ?line ok = send(Sock,[size16(M),M]), + R = put16(Port), + ?line {ok,R} = recv(Sock, length(R)), + ?line ok = close(RSock), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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(), + ?line {ok,RSock} = register_node("foo"), + ?line {ok,Sock} = connect(), + M = [?EPMD_STOP_REQ,"foo"], + ?line ok = send(Sock,[size16(M),M]), + R = "STOPPED", + ?line {ok,R} = recv(Sock,length(R)), + ?line ok = close(RSock), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unregister_others_name_2(doc) -> + ["Unregister name of other node"]; +unregister_others_name_2(suite) -> + []; +unregister_others_name_2(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + M = [?EPMD_STOP_REQ,"xxx42"], + ?line ok = send(Sock,[size16(M),M]), + R = "NOEXIST", + ?line {ok,R} = recv(Sock,length(R)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +register_overflow(doc) -> + ["Register too many, clean and redo 10 times"]; +register_overflow(suite) -> + []; +register_overflow(Config) when list(Config) -> + Dog = ?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + LongDog = test_server:timetrap(?LONG_TEST_TIMEOUT), + ?line ok = epmdrun(), + ?line Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), + Count = length(Conn), + ?line ok = unregister_many(Conn), + sleep(?MEDIUM_PAUSE), + test_server:format("Limit was ~w names, now reg/unreg all 10 times~n", + [Count]), + ?line ok = register_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = rregister_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = register_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = rregister_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = register_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = rregister_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = register_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = rregister_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = register_repeat(Count), + sleep(?MEDIUM_PAUSE), + ?line ok = rregister_repeat(Count), + test_server:timetrap_cancel(LongDog), + ok. + +register_repeat(Count) -> + Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), + ok = unregister_many(Conn), + if + length(Conn) == Count -> + ok; + true -> + error + end. + +rregister_repeat(Count) -> + Conn = register_many(1, ?REG_REPEAT_LIM, "foo"), + ok = unregister_many(lists:reverse(Conn)), + if + length(Conn) == Count -> + ok; + true -> + error + end. + +% Return count of successful registrations + +register_many(I, N, _Prefix) when I > N -> + test_server:format("Done with all ~n", []), + []; +register_many(I, N, Prefix) -> + Name = gen_name(Prefix, I), + Port = ?DUMMY_PORT + I, % Just make it up + case register_node(Name, Port) of + {ok,Sock} -> + [{Name,Port,Sock} | register_many(I + 1, N, Prefix)]; + Any -> + test_server:format("Can't register: ~w of 1..~w ~w~n", + [Name,N,Any]), + [] + end. + +unregister_many([]) -> + ok; +unregister_many([{Name,_Port,Sock} | Socks]) -> + case close(Sock) of + ok -> + unregister_many(Socks); + Any -> + test_server:format("Can't unregister: ~w reason ~w~n", [Name,Any]), + error + end. + +gen_name(Str,Int) -> + Str ++ integer_to_list(Int). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +no_data(doc) -> + ["Open but send no data"]; +no_data(suite) -> + []; +no_data(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + sleep(?LONG_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +one_byte(doc) -> + ["Send one byte only"]; +one_byte(suite) -> + []; +one_byte(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + ?line ok = send(Sock,[0]), + sleep(?LONG_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +two_bytes(doc) -> + ["Send packet size only"]; +two_bytes(suite) -> + []; +two_bytes(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + ?line ok = send(Sock,[put16(3)]), + sleep(?LONG_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +partial_packet(doc) -> + ["Got only part of a packet"]; +partial_packet(suite) -> + []; +partial_packet(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + ?line ok = send(Sock,[put16(100),"only a few bytes"]), + sleep(?LONG_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +zero_length(doc) -> + ["Invalid zero packet size"]; +zero_length(suite) -> + []; +zero_length(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + ?line ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), + sleep(?MEDIUM_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +too_large(doc) -> + ["Invalid large packet"]; +too_large(suite) -> + []; +too_large(Config) when 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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + M = [?EPMD_ALIVE_REQ, 42], + ?line ok = send(Sock, [size16(M), M]), + sleep(?MEDIUM_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +alive_req_too_small_2(doc) -> + ["Try to register but not enough data"]; +alive_req_too_small_2(suite) -> + []; +alive_req_too_small_2(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT)], + ?line ok = send(Sock, [size16(M), M]), + sleep(?MEDIUM_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +alive_req_too_large(doc) -> + ["Try to register but node name too large"]; +alive_req_too_large(suite) -> + []; +alive_req_too_large(Config) when list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Sock} = connect(), + L = [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT), L], + ?line ok = send(Sock, [size16(M), M]), + sleep(?MEDIUM_PAUSE), + ?line closed = recv(Sock,1), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Terminate all tests with killing epmd. + +cleanup() -> + sleep(?MEDIUM_PAUSE), + case connect() of + {ok,Sock} -> + M = [?EPMD_KILL_REQ], + send(Sock, [size16(M), M]), + recv(Sock,length("OK")), + close(Sock), + sleep(?MEDIUM_PAUSE); + _ -> + true + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Normal debug start of epmd + +epmdrun() -> + case os:find_executable(epmd) of + false -> + {error, {could_not_find_epmd_in_path}}; + Path -> + epmdrun(Path) + end. + +epmdrun(Epmd) -> + %% test_server:format("epmdrun() => Epmd = ~p",[Epmd]), + osrun(Epmd ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Start an external process + +osrun(Cmd) -> + _ = open_port({spawn, Cmd}, []), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Wrappers of TCP functions + +% These two functions is the interface for connect. +% Passive mode is the default + +connect() -> + connect(?PORT, passive). + +connect_active() -> + connect(?PORT, active). + + +% Try a few times before giving up + +connect(Port, Mode) -> + case connect_repeat(?CONN_RETRY, Port, Mode) of + {ok,Sock} -> + {ok,Sock}; + {error,timeout} -> + timeout; + {error,Reason} -> + test_server:format("connect: error: ~w~n",[Reason]), + error; + Any -> + test_server:format("connect: unknown message: ~w~n",[Any]), + exit(1) + end. + + +% Try a few times before giving up. Pause a small time between +% each try. + +connect_repeat(1, Port, Mode) -> + connect_mode(Port, Mode); +connect_repeat(Retry, Port, Mode) -> + case connect_mode(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); + 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}], + ?CONN_TIMEOUT). + + +close(Sock) -> + case gen_tcp:close(Sock) of + {error,_} -> + error; + ok -> + ok; + Any -> + test_server:format("unknown message: ~w~n",[Any]), + exit(1) + end. + +recv(Sock, Len) -> + recv(Sock, Len, ?RECV_TIMEOUT). + +recv(Sock, Len, Timeout) -> + case gen_tcp:recv(Sock, Len, Timeout) of + {ok,[]} -> % Should not be the case + recv(Sock, 1, 1); % any longer + {ok,Data} -> + {ok,Data}; + {error,timeout} -> + timeout; + {error,closed} -> + closed; + {error,_}=Error -> + Error; + Any -> + test_server:format("unknown message: ~w~n",[Any]), + exit(1) + end. + +%% Send data to socket. The list can be non flat and contain +%% the atom 'd' or tuple {d,Seconds} where this is delay +%% put in between the sent characters. + +send(Sock, SendSpec) -> + case send(SendSpec, [], Sock) of + {ok,[]} -> + ok; + {ok,RevBytes} -> + send_direct(Sock, lists:reverse(RevBytes)); + Any -> + Any + end. + + +% If an error, return immediately +% Collect real characters in the first argument to form +% a string to send. Only perform "actions", like a delay, +% when this argument is empty. + +send([], RevBytes, _Sock) -> + {ok,RevBytes}; +send([Byte | Spec], RevBytes, Sock) when integer(Byte) -> + send(Spec, [Byte | RevBytes], Sock); +send([List | Spec], RevBytes, Sock) when list(List) -> + case send(List, RevBytes, Sock) of + {ok,Left} -> + send(Spec, Left, Sock); + Other -> + Other + end; +send([d | Spec], RevBytes, Sock) -> + send([{d,1000} | Spec], RevBytes, Sock); +send([{d,S} | Spec], RevBytes, Sock) -> + case send_direct(Sock, lists:reverse(RevBytes)) of + ok -> + timer:sleep(S), + send(Spec, [], Sock); + Any -> + Any + end. + +%%%% + +send_direct(Sock, Bytes) -> + case gen_tcp:send(Sock, Bytes) of + ok -> + ok; + {error, closed} -> + closed; + {error, _Reason} -> + error; + Any -> + test_server:format("unknown message: ~w~n",[Any]), + Any + end. + +sleep(MilliSeconds) -> + timer:sleep(MilliSeconds). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +put16(N) -> + [N bsr 8, N band 16#ff]. + +size16(List) -> + N = flat_count(List, 0), + [N bsr 8, N band 16#ff]. + +flat_count([H|T], N) when is_integer(H) -> + flat_count(T, N+1); +flat_count([H|T], N) when is_list(H) -> + flat_count(T, flat_count(H, N)); +flat_count([_|T], N) -> + flat_count(T, N); +flat_count([], N) -> N. + |