From dbd7a975ec64dc52af704970109f8c637600b088 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 22 Apr 2021 18:54:41 -0700 Subject: telnetd: handle TIME_WAIT better. After a network outage, a long-running telnetd was spinning trying to read from a socket that was in TIME_WAIT. It's easy to reproduce this by using the regular telnet client and typing ^]^D to exit abruptly. I don't think these sockets should ever have been non-blocking, and we want to give up on the client if we hit EOF. All of this needs rewriting to be less complicated (and not use select(2)), but this seems to be a minimal fix for the spin without harming normal usage (where by "usage" I mean "testing the telnet client"). --- toys/pending/telnetd.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/toys/pending/telnetd.c b/toys/pending/telnetd.c index 7bfbf316..032104f2 100644 --- a/toys/pending/telnetd.c +++ b/toys/pending/telnetd.c @@ -149,23 +149,16 @@ static int new_session(int sockfd) { char *argv_login[] = {NULL, "-h", NULL, NULL}; char tty_name[30]; //tty name length. - int fd, flags, i = 1; + int fd, i = 1; char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS, IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA }; struct sockaddr_storage sa; socklen_t sl = sizeof(sa); setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)); - flags = fcntl(sockfd, F_GETFL); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - if (FLAG(i)) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK); writeall(FLAG(i)?1:sockfd, intial_iacs, sizeof(intial_iacs)); - if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) { - flags = fcntl(fd, F_GETFL); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - return fd; - } + if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) return fd; if (TT.fork_pid < 0) perror_exit("fork"); if (getpeername(sockfd, (void *)&sa, &sl)) perror_exit("getpeername"); @@ -362,7 +355,15 @@ void telnetd_main(void) } if (FD_ISSET(tm->new_fd, &rd)) { if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail, - BUFSIZE-tm->buff2_avail)) <= 0) break; + BUFSIZE-tm->buff2_avail)) <= 0) { + // The other side went away without a proper shutdown. Happens if + // you exit telnet via ^]^D, leaving the socket in TIME_WAIT. + xclose(tm->new_fd); + tm->new_fd = -1; + xclose(tm->pty_fd); + tm->pty_fd = -1; + break; + } c = handle_iacs(tm, c, tm->pty_fd); tm->buff2_avail += c; if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, @@ -405,6 +406,7 @@ void telnetd_main(void) if (!tm) error_exit("unexpected reparenting of %d", pid); if (FLAG(i)) exit(EXIT_SUCCESS); + if (!prev) session_list = session_list->next; else prev->next = tm->next; xclose(tm->pty_fd); -- cgit v1.2.3