[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
personal/stbuehler/mod-csrf-old
Glenn Strauss 7 years ago
parent 1de652f40b
commit 4bc06bfc0b

@ -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;
}

@ -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
}

@ -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

@ -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) {

Loading…
Cancel
Save