The client finishes sending, the proxy sees EOF, and the reply disappears. Closing both proxy sockets immediately can cause exactly that sequence. In a TCP relay, an orderly end in one direction does not say the other direction has finished.

Consider a client that sends a request, shuts down its sending side and waits for the answer:

client -- request, then FIN --> proxy --> service
client <-------- reply ------- proxy <-- service

The proxy has two TCP connections and two independently moving byte streams. It needs to carry the end-of-input indication onward, after carrying the bytes that precede it.

Drain the queue before forwarding EOF

For a nonzero-size receive on a TCP stream, a return of zero indicates an orderly end of that incoming stream. Mark that read direction finished. Stop waiting for more input from it, but retain bytes already queued for the opposite socket.

Once the client-to-service queue has drained, call shutdown(service_socket, SHUT_WR) on a POSIX socket. That ends further transmission toward the service while leaving the service-to-client direction available. When the service finishes its reply, apply the mirrored operation after draining the reply queue.

Closing the descriptors belongs at the point where both directions and their pending buffers are finished, or where an error or cancellation requires termination. Keep a bounded timeout for a peer that never completes its remaining half.

Readiness is an invitation to read

A readable socket can have data or an EOF condition. Call recv and interpret its result. Positive counts are bytes to forward; zero is the directional end. On a nonblocking socket, EAGAIN or EWOULDBLOCK means there is currently no more input, while EINTR may require retrying. Neither is the same event as EOF.

Do not search the returned byte buffer for a FIN flag. TCP delivers an ordered byte stream to this API; packet boundaries and the FIN segment are not ordinary application payload.

This is the lifecycle of a raw byte tunnel. An HTTP proxy that parses messages still has to respect HTTP framing, and a TLS-terminating proxy has a TLS shutdown protocol too. Keep those protocol decisions above the directional TCP relay.

Erthyglow kevrennek