From f08b8139fc7370224c59bc3178b887810b98592b Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 9 Jan 2017 13:16:32 +1030 Subject: [PATCH] io: handle errors on listening file descriptors. While investigating the previous patch, a bug caused poll to return POLLHUP on the listening socket, which caused us to spin. Signed-off-by: Rusty Russell --- ccan/io/io.h | 3 +- ccan/io/poll.c | 7 ++- .../test/run-22-POLLHUP-on-listening-socket.c | 61 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 ccan/io/test/run-22-POLLHUP-on-listening-socket.c diff --git a/ccan/io/io.h b/ccan/io/io.h index 8efc0024..54300ce4 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -109,7 +109,8 @@ void io_set_finish_(struct io_conn *conn, * @arg: the argument to @init. * * When @fd becomes readable, we accept(), create a new connection, - * (tal'ocated off @ctx) and pass that to init(). + * (tal'ocated off @ctx) and pass that to init(). Note that if there is + * an error on this file descriptor, it will be freed. * * Returns NULL on error (and sets errno). * diff --git a/ccan/io/poll.c b/ccan/io/poll.c index 98a64f42..229f7ce9 100644 --- a/ccan/io/poll.c +++ b/ccan/io/poll.c @@ -282,9 +282,14 @@ void *io_loop(struct timers *timers, struct timer **expired) break; if (fds[i]->listener) { + struct io_listener *l = (void *)fds[i]; if (events & POLLIN) { - accept_conn((void *)c); + accept_conn(l); r--; + } else if (events & (POLLHUP|POLLNVAL|POLLERR)) { + r--; + errno = EBADF; + io_close_listener(l); } } else if (events & (POLLIN|POLLOUT)) { r--; diff --git a/ccan/io/test/run-22-POLLHUP-on-listening-socket.c b/ccan/io/test/run-22-POLLHUP-on-listening-socket.c new file mode 100644 index 00000000..e3b6c413 --- /dev/null +++ b/ccan/io/test/run-22-POLLHUP-on-listening-socket.c @@ -0,0 +1,61 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +#define PORT "65022" + +static int make_listen_fd(const char *port, struct addrinfo **info) +{ + int fd, on = 1; + struct addrinfo *addrinfo, hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + + if (getaddrinfo(NULL, port, &hints, &addrinfo) != 0) + return -1; + + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (fd < 0) + return -1; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) { + close(fd); + return -1; + } + if (listen(fd, 1) != 0) { + close(fd); + return -1; + } + *info = addrinfo; + return fd; +} + +int main(void) +{ + struct addrinfo *addrinfo = NULL; + int fd; + + /* This is how many tests you plan to run */ + plan_tests(1); + fd = make_listen_fd(PORT, &addrinfo); + freeaddrinfo(addrinfo); + io_new_listener(NULL, fd, io_never, NULL); + + /* Anyone could do this; a child doing it will cause poll to return + * POLLHUP only! */ + shutdown(fd, SHUT_RDWR); + ok1(io_loop(NULL, NULL) == NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} -- 2.39.2