Skip to content

Fix FIN_WAIT_2 accumulation by draining sockets before close#600

Open
renaudallard wants to merge 1 commit intotinyproxy:masterfrom
renaudallard:fix-finwait2-leak
Open

Fix FIN_WAIT_2 accumulation by draining sockets before close#600
renaudallard wants to merge 1 commit intotinyproxy:masterfrom
renaudallard:fix-finwait2-leak

Conversation

@renaudallard
Copy link

Problem

On OpenBSD, proxied connections accumulate in FIN_WAIT_2 state and are
never reaped. When enough build up the proxy stalls.

tcp 0 0 172.20.66.101.8080 172.20.66.3.17914 FIN_WAIT_2
tcp 0 0 172.20.66.101.8080 172.20.66.3.13047 FIN_WAIT_2
tcp 0 0 172.20.66.101.8080 172.20.66.3.30194 FIN_WAIT_2
...

After shutdown(client_fd, SHUT_WR) sends a FIN to the client,
conn_destroy_contents() calls close() without waiting for
the remote FIN. The socket is orphaned while still in FIN_WAIT_2.

On Linux this is masked by net.ipv4.tcp_fin_timeout (default 60 s)
which aggressively reaps orphaned FIN_WAIT_2 sockets. OpenBSD has no
equivalent aggressive timeout, so without SO_KEEPALIVE these sockets
persist indefinitely.

The problem also affects the timeout and poll-error return paths in
relay_connection(), which skip shutdown() entirely and go
straight to close().

Fix

  • Add close_socket() helper in conns.c that performs a proper
    TCP close handshake: shutdown(SHUT_WR), drain with a 2-second
    SO_RCVTIMEO, then close(). This gives the remote side time
    to send its FIN before the socket is orphaned.
  • Use it in conn_destroy_contents() for both client and server
    file descriptors, covering all exit paths from relay_connection().
  • Add the missing shutdown(server_fd, SHUT_WR) in
    relay_connection() after flushing remaining data to the upstream
    server.

Testing

Tested on OpenBSD. After the fix, connections transition through
TIME_WAIT normally instead of accumulating in FIN_WAIT_2.

After shutdown(SHUT_WR) sends a FIN to the remote, tinyproxy was calling
close() without waiting for the remote FIN.  The socket was orphaned in
FIN_WAIT_2 state.  On Linux this is masked by tcp_fin_timeout reaping
orphaned sockets after 60s.  On OpenBSD, without SO_KEEPALIVE, these
sockets persist indefinitely and accumulate until the proxy stalls.

Add close_socket() that calls shutdown(SHUT_WR), then drains the socket
with a 2-second receive timeout to allow the remote FIN to arrive before
calling close().  Use it in conn_destroy_contents() for both client and
server file descriptors, covering all exit paths from relay_connection()
including idle timeout and poll error returns.

Also add the missing shutdown(server_fd, SHUT_WR) in relay_connection()
after flushing remaining data to the upstream server, so the server
receives a proper FIN rather than relying on the implicit close().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant