Skip to content

Client half-close: payload of a FIN segment is dropped, and our FIN is sent immediately (no half-close) #111

Description

@frontierlabs-admin

Affected: 1.0.1 (crates.io). 1.0.0 has the same arms.

Two related problems in TcpState::Established / CloseWait handling, both seen when a client sends data and then calls shutdown(SHUT_WR) (a TCP half-close) while the application on the ipstack side still has a reply to send. Related to #89, which covers the opposite direction (server shuts down first).

1. A data segment that carries FIN loses its payload

Linux puts the FIN on the last data segment when shutdown(SHUT_WR) is called with data still queued, so the segment arrives as ACK|FIN (or ACK|PSH|FIN) with a non-empty payload. In Established, the ACK | FIN arm takes the FIN without queuing the payload, and the ACK | PSH arm discards out-of-order data instead of queuing it. The last 100-500 bytes of the client's stream never reach the reader. With a 4 MiB upload the loss was 144 to 448 bytes per run.

2. A client FIN closes the whole connection (no half-close)

On a client FIN in Established, with nothing in flight, the stack immediately sends its own ACK|FIN and moves to LastAck; in CloseWait a timer forces the same. The server-to-client direction is therefore closed the moment the client half-closes, so a reply still being written is cut off (an echo service returned 216,612 of 4,194,304 bytes). poll_read also returns Pending rather than EOF in CloseWait, so a reader waits for the timer. Expected: ACK the FIN, report EOF to the reader, keep the write side open, and send our FIN when the application calls poll_shutdown (or drops the stream), with the existing timeout as a backstop.

Reproduction

A client that writes N bytes and then half-closes (e.g. sendall(data); sock.shutdown(SHUT_WR)) to a stream that copies its input back to its output (tokio::io::copy_bidirectional-style echo), then reads to EOF. Expected: N bytes echoed, then EOF. Actual: fewer than N bytes (problems 1 and 2), or a stall until the idle timeout.

What we did

We carry a patch to src/stream/tcp.rs (about 300 changed lines, with tests that fail when each change is reverted individually). It queues the payload of a FIN segment before taking the FIN, treats ACK|PSH as ACK, gives CloseWait readers their already-acknowledged bytes and then EOF, and sends our FIN from poll_shutdown/drop instead of from the client's FIN. Happy to turn it into a PR if that is useful; tell us how you would like it split.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions