From 4bc06bfc0b72b92c67f21ac0dc7de7d67be73728 Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Sat, 6 Aug 2016 04:11:16 -0400 Subject: [PATCH] [core] check if client half-closed TCP if POLLHUP (#2743) Check if client half-closed TCP connection if POLLHUP is received. This more robustly handles if client called shutdown(fd, SHUT_WR). This patch reverts commit:ab05eb7c which should now be handled properly. (Time will tell.) x-ref: "1.4.40/41 mod_proxy, mod_scgi may trigger POLLHUP on *BSD,Darwin" https://redmine.lighttpd.net/issues/2743 --- src/connections.c | 58 ++++++++++++++++----------------------------- src/fdevent.c | 32 +++++++++++++++++++++++++ src/fdevent.h | 3 +++ tests/LightyTest.pm | 1 + 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/connections.c b/src/connections.c index 4cd21397..d933551f 100644 --- a/src/connections.c +++ b/src/connections.c @@ -943,43 +943,6 @@ static handler_t connection_handle_fdevent(server *srv, void *context, int reven } - if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) { - /* looks like an error */ - - /* FIXME: revents = 0x19 still means that we should read from the queue */ - if (revents & FDEVENT_HUP) { - if (con->state == CON_STATE_CLOSE) { - con->close_timeout_ts = srv->cur_ts - (HTTP_LINGER_TIMEOUT+1); - } else { - /* sigio reports the wrong event here - * - * there was no HUP at all - */ -#ifdef USE_LINUX_SIGIO - if (srv->ev->in_sigio == 1) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "connection closed: poll() -> HUP", con->fd); - } else { - connection_set_state(srv, con, CON_STATE_ERROR); - } -#else - connection_set_state(srv, con, CON_STATE_ERROR); -#endif - - } - } else if (revents & FDEVENT_ERR) { - /* error, connection reset, whatever... we don't want to spam the logfile */ -#if 0 - log_error_write(srv, __FILE__, __LINE__, "sd", - "connection closed: poll() -> ERR", con->fd); -#endif - connection_set_state(srv, con, CON_STATE_ERROR); - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "connection closed: poll() -> ???", revents); - } - } - if (con->state == CON_STATE_READ) { connection_handle_read_state(srv, con); } @@ -1008,6 +971,27 @@ static handler_t connection_handle_fdevent(server *srv, void *context, int reven } } + + /* attempt (above) to read data in kernel socket buffers + * prior to handling FDEVENT_HUP and FDEVENT_ERR */ + + if ((revents & ~(FDEVENT_IN | FDEVENT_OUT)) && con->state != CON_STATE_ERROR) { + if (con->state == CON_STATE_CLOSE) { + con->close_timeout_ts = srv->cur_ts - (HTTP_LINGER_TIMEOUT+1); + } else if (revents & FDEVENT_HUP) { + if (fdevent_is_tcp_half_closed(con->fd)) { + con->keep_alive = 0; + } else { + connection_set_state(srv, con, CON_STATE_ERROR); + } + } else if (revents & FDEVENT_ERR) { /* error, connection reset */ + connection_set_state(srv, con, CON_STATE_ERROR); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed: poll() -> ???", revents); + } + } + return HANDLER_FINISHED; } diff --git a/src/fdevent.c b/src/fdevent.c index 6d5096c8..756c8e6a 100644 --- a/src/fdevent.c +++ b/src/fdevent.c @@ -250,3 +250,35 @@ int fdevent_event_next_fdndx(fdevents *ev, int ndx) { return -1; } + +#include +#if (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__FreeBSD__) || defined(__NetBSD__) \ + || defined(__OpenBSD__) || defined(__DragonflyBSD__) +#include +#endif + +/* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */ +int fdevent_is_tcp_half_closed(int fd) { + #ifdef TCP_CONNECTION_INFO /* Darwin */ + struct tcp_connection_info tcpi; + socklen_t tlen = sizeof(tcpi); + return (0 == getsockopt(fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &tcpi, &tlen) + && tcpi.tcpi_state == TCPS_CLOSE_WAIT); + #elif defined(TCPS_CLOSE_WAIT) /* FreeBSD, NetBSD (not present in OpenBSD) */ + struct tcp_info tcpi; + socklen_t tlen = sizeof(tcpi); + return (0 == getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpi, &tlen) + && tcpi.tcpi_state == TCPS_CLOSE_WAIT); + #elif defined(TCP_INFO) /* Linux */ + struct tcp_info tcpi; + socklen_t tlen = sizeof(tcpi);/*SOL_TCP == IPPROTO_TCP*/ + return (0 == getsockopt(fd, SOL_TCP, TCP_INFO, &tcpi, &tlen) + && tcpi.tcpi_state == TCP_CLOSE_WAIT); + #else + UNUSED(fd); + /*(0 != getpeername() error might indicate TCP RST, but success + * would not differentiate between half-close and full-close)*/ + return 0; /* false (not half-closed) or TCP state unknown */ + #endif +} diff --git a/src/fdevent.h b/src/fdevent.h index cab04291..4dc1d3cc 100644 --- a/src/fdevent.h +++ b/src/fdevent.h @@ -214,4 +214,7 @@ int fdevent_solaris_port_init(fdevents *ev); int fdevent_freebsd_kqueue_init(fdevents *ev); int fdevent_libev_init(fdevents *ev); +/* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */ +int fdevent_is_tcp_half_closed(int fd); + #endif diff --git a/tests/LightyTest.pm b/tests/LightyTest.pm index b975d651..129acd87 100644 --- a/tests/LightyTest.pm +++ b/tests/LightyTest.pm @@ -223,6 +223,7 @@ sub handle_http { print $remote $_.$BLANK; diag("\n<< ".$_) if $is_debug; } + shutdown($remote, 1) if ($^O ne "openbsd"); # I've stopped writing data } else { diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug; foreach(@request) {