void run(EpmdVars *g) { struct EPMD_SOCKADDR_IN iserv_addr[MAX_LISTEN_SOCKETS]; int listensock[MAX_LISTEN_SOCKETS]; int num_sockets; int i; int opt; unsigned short sport = g->port; node_init(g); g->conn = conn_init(g); #ifdef HAVE_SYSTEMD_DAEMON if (g->is_systemd) { int n; dbg_printf(g,2,"try to obtain sockets from systemd"); n = sd_listen_fds(0); if (n < 0) { dbg_perror(g,"cannot obtain sockets from systemd"); epmd_cleanup_exit(g,1); } else if (n == 0) { dbg_tty_printf(g,0,"systemd provides no sockets"); epmd_cleanup_exit(g,1); } else if (n > MAX_LISTEN_SOCKETS) { dbg_tty_printf(g,0,"cannot listen on more than %d IP addresses", MAX_LISTEN_SOCKETS); epmd_cleanup_exit(g,1); } num_sockets = n; for (i = 0; i < num_sockets; i++) { g->listenfd[i] = listensock[i] = SD_LISTEN_FDS_START + i; } } else { #endif /* HAVE_SYSTEMD_DAEMON */ dbg_printf(g,2,"try to initiate listening port %d", g->port); if (g->addresses != NULL && /* String contains non-separator characters if: */ g->addresses[strspn(g->addresses," ,")] != '\000') { char *tmp; char *token; int loopback_ok = 0; if ((tmp = (char *)malloc(strlen(g->addresses) + 1)) == NULL) { dbg_perror(g,"cannot allocate memory"); epmd_cleanup_exit(g,1); } strcpy(tmp,g->addresses); for(token = strtok(tmp,", "), num_sockets = 0; token != NULL; token = strtok(NULL,", "), num_sockets++) { struct EPMD_IN_ADDR addr; #ifdef HAVE_INET_PTON int ret; if ((ret = inet_pton(FAMILY,token,&addr)) == -1) { dbg_perror(g,"cannot convert IP address to network format"); epmd_cleanup_exit(g,1); } else if (ret == 0) #elif !defined(EPMD6) if ((addr.EPMD_S_ADDR = inet_addr(token)) == INADDR_NONE) #endif { dbg_tty_printf(g,0,"cannot parse IP address \"%s\"",token); epmd_cleanup_exit(g,1); } if (IS_ADDR_LOOPBACK(addr)) loopback_ok = 1; if (num_sockets - loopback_ok == MAX_LISTEN_SOCKETS - 1) { dbg_tty_printf(g,0,"cannot listen on more than %d IP addresses", MAX_LISTEN_SOCKETS); epmd_cleanup_exit(g,1); } SET_ADDR(iserv_addr[num_sockets],addr.EPMD_S_ADDR,sport); } free(tmp); if (!loopback_ok) { SET_ADDR(iserv_addr[num_sockets],EPMD_ADDR_LOOPBACK,sport); num_sockets++; } } else { SET_ADDR(iserv_addr[0],EPMD_ADDR_ANY,sport); num_sockets = 1; } #ifdef HAVE_SYSTEMD_DAEMON } #endif /* HAVE_SYSTEMD_DAEMON */ #if !defined(__WIN32__) /* 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); #endif /* * Initialize number of active file descriptors. * Stdin, stdout, and stderr are still open. */ g->active_conn = 3 + num_sockets; g->max_conn -= num_sockets; FD_ZERO(&g->orig_read_mask); g->select_fd_top = 0; #ifdef HAVE_SYSTEMD_DAEMON if (g->is_systemd) for (i = 0; i < num_sockets; i++) select_fd_set(g, listensock[i]); else { #endif /* HAVE_SYSTEMD_DAEMON */ for (i = 0; i < num_sockets; i++) { if ((listensock[i] = socket(FAMILY,SOCK_STREAM,0)) < 0) { dbg_perror(g,"error opening stream socket"); epmd_cleanup_exit(g,1); } g->listenfd[i] = listensock[i]; /* * 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__) opt = 1; if (setsockopt(listensock[i],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. */ #if (defined(__WIN32__) || defined(NO_FCNTL)) opt = 1; /* Gives warning in VxWorks */ if (ioctl(listensock[i], FIONBIO, &opt) != 0) #else opt = fcntl(listensock[i], F_GETFL, 0); if (fcntl(listensock[i], F_SETFL, opt | O_NONBLOCK) == -1) #endif /* __WIN32__ || VXWORKS */ dbg_perror(g,"failed to set non-blocking mode of listening socket %d", listensock[i]); if (bind(listensock[i], (struct sockaddr*) &iserv_addr[i], sizeof(iserv_addr[i])) < 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); } } if(listen(listensock[i], SOMAXCONN) < 0) { dbg_perror(g,"failed to listen on socket"); epmd_cleanup_exit(g,1); } select_fd_set(g, listensock[i]); } #ifdef HAVE_SYSTEMD_DAEMON } sd_notifyf(0, "READY=1\n" "STATUS=Processing port mapping requests...\n" "MAINPID=%lu", (unsigned long) getpid()); #endif /* HAVE_SYSTEMD_DAEMON */ 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->select_fd_top, &read_mask, (fd_set *)0,(fd_set *)0,&timeout)) < 0) { dbg_perror(g,"error in select "); switch (errno) { case EAGAIN: case EINTR: break; default: epmd_cleanup_exit(g,1); } } else { time_t now; if (ret == 0) { FD_ZERO(&read_mask); } if (g->delay_accept) { /* Test of busy server */ sleep(g->delay_accept); } for (i = 0; i < num_sockets; i++) if (FD_ISSET(listensock[i],&read_mask)) { if (do_accept(g, listensock[i]) && 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]); } } } } } }
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); node_unreg_sock(g,s->fd); epmd_conn_close(g,s); } 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 */ } }
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__) /* 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. */ #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 */ 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); } 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"); if(listen(listensock, SOMAXCONN) < 0) { dbg_perror(g,"failed to listen on socket"); epmd_cleanup_exit(g,1); } 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 "); switch (errno) { case EAGAIN: case EINTR: break; default: epmd_cleanup_exit(g,1); } } 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]); } } } } } }