diff --git a/src/conns.c b/src/conns.c index 19aaa49c..b70fbdeb 100644 --- a/src/conns.c +++ b/src/conns.c @@ -28,6 +28,7 @@ #include "conns.h" #include "heap.h" #include "log.h" +#include "sock.h" #include "stats.h" void conn_struct_init(struct conn_s *connptr) { @@ -82,14 +83,10 @@ void conn_destroy_contents (struct conn_s *connptr) assert (connptr != NULL); if (connptr->client_fd != -1) - if (close (connptr->client_fd) < 0) - log_message (LOG_INFO, "Client (%d) close message: %s", - connptr->client_fd, strerror (errno)); + close_socket (connptr->client_fd); connptr->client_fd = -1; if (connptr->server_fd != -1) - if (close (connptr->server_fd) < 0) - log_message (LOG_INFO, "Server (%d) close message: %s", - connptr->server_fd, strerror (errno)); + close_socket (connptr->server_fd); connptr->server_fd = -1; if (connptr->cbuffer) diff --git a/src/reqs.c b/src/reqs.c index 0998324d..12f073b1 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -1259,6 +1259,7 @@ static void relay_connection (struct conn_s *connptr) if (write_buffer (connptr->server_fd, connptr->cbuffer) < 0) break; } + shutdown (connptr->server_fd, SHUT_WR); return; } diff --git a/src/sock.c b/src/sock.c index ec8ad4c8..5e3f080d 100644 --- a/src/sock.c +++ b/src/sock.c @@ -212,6 +212,40 @@ int opensock (const char *host, int port, const char *bind_to) return sockfd; } +/* + * Gracefully close a socket by completing the TCP close handshake. + * + * shutdown(SHUT_WR) sends our FIN while the fd stays open, then we + * drain whatever the peer still sends until read() returns 0, which + * signals that the peer's FIN has arrived. Only then do we close(). + * By that point the four-way handshake is complete and the socket + * moves to TIME_WAIT, which the kernel reaps on its own. + * + * Calling close() on a socket still in FIN_WAIT_2 orphans it. Linux + * reaps orphaned FIN_WAIT_2 sockets via net.ipv4.tcp_fin_timeout, but + * OpenBSD has no equivalent, so they accumulate until the proxy + * stalls. See RFC 793 sec. 3.5 and Stevens, UNIX Network + * Programming Vol. 1 sec. 6.6. + */ +void close_socket (int fd) +{ + char drain[4096]; + ssize_t n; + struct timeval tv; + + shutdown (fd, SHUT_WR); + + tv.tv_sec = 10; + tv.tv_usec = 0; + setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv)); + + do { + n = read (fd, drain, sizeof (drain)); + } while (n > 0 || (n < 0 && errno == EINTR)); + + close (fd); +} + /** * Try to listen on one socket based on the addrinfo * as returned from getaddrinfo. diff --git a/src/sock.h b/src/sock.h index 9763490d..86d7e7f0 100644 --- a/src/sock.h +++ b/src/sock.h @@ -52,6 +52,7 @@ union sockaddr_union { extern int opensock (const char *host, int port, const char *bind_to); extern int listen_sock (const char *addr, uint16_t port, sblist* listen_fds); +extern void close_socket (int fd); extern void set_socket_timeout(int fd);