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(-)
From 622beff6290532a14171dd6ab2602748d6a71e25 Mon Sep 17 00:00:00 2001 From: Elliott Hughes <[email protected]> Date: Thu, 22 Apr 2021 18:54:41 -0700 Subject: [PATCH] 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); -- 2.31.1.498.g6c1eba8ee3d-goog
_______________________________________________ Toybox mailing list [email protected] http://lists.landley.net/listinfo.cgi/toybox-landley.net
