diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/connections.c | 58 | ||||
-rw-r--r-- | src/fdevent.c | 32 | ||||
-rw-r--r-- | src/fdevent.h | 3 |
3 files changed, 56 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 <netinet/tcp.h> +#if (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__FreeBSD__) || defined(__NetBSD__) \ + || defined(__OpenBSD__) || defined(__DragonflyBSD__) +#include <netinet/tcp_fsm.h> +#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 |