fix: txring_put() refuses over-MTU packets instead of truncating them (#1108) - #1113
Merged
Conversation
…#1108) Found while building the MTU-matrix integration tests (#1097): when a packet exceeded the interface's MTU, txring_put() copied in only the first max_len bytes and queued that truncated result as though it were the whole packet. The kernel has no way to know it's short, so the corrupted frame reached the wire - while send_packets.c's caller-side check (comparing the requested length against txring_put()'s truncated return value) counted it a failure. "0 successful, N failed" described the bookkeeping, not what the interface actually transmitted: /sys/class/net/<iface>/statistics/tx_packets still advanced. Moved the size check to the front of txring_put(), before it waits for a ring slot, and changed it to refuse outright - return -1 with errno set to EMSGSIZE - rather than clamp and continue. Nothing is written into the ring and no frame the kernel could transmit ever gets queued, so "failed" now means what it says. sendpacket()'s existing generic error handling (src/common/sendpacket.c) already treats EMSGSIZE like any other hard failure - sets a descriptive message, counts it, does not retry (retries are reserved for EAGAIN/ENOBUFS, which would be pointless here: a packet that doesn't fit this MTU never will) - so no caller-side changes were needed. Fragmenting instead of refusing is the documented TODO this replaces, and is still not attempted here - refusing is the smaller, no-worse-than-before change; actually fragmenting is a separate feature, not a bug fix. test/unit/test_txring.c's oversized-packet case updated to match: asserts the refusal (-1/EMSGSIZE) and that the frame is left untouched (TP_STATUS_AVAILABLE), rather than the old clamped-length/partial-copy behavior. Verified against the issue's own repro (MTU 1280, test.pcap up to 1514 bytes): "Truncated packets" is now 0 rather than 32, and packets that do fit the MTU are no longer stuck in the ring behind ones that don't, so "Successful packets" reports the packets that actually went out instead of 0. Full suite: 164/164 unit assertions, sudo make tcpreplay 19/19, and CI's exact asan job config (ASan+UBSan, alignment included) 78/78 with 0 sanitizer reports. Also verified under the plain non-sanitizer build and CMake. Fixes #1108
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1108.
txring_put()used to copy in only the firstmax_lenbytes of an over-MTU packet and queue that truncated result as though it were the whole packet. The kernel has no way to know it's short, so the corrupted frame reached the wire - whilesend_packets.c's caller-side check (comparing the requested length againsttxring_put()'s truncated return value) counted it a failure. "0 successful, N failed" described the bookkeeping, not what the interface actually transmitted -/sys/class/net/<iface>/statistics/tx_packetsstill advanced.The fix
Chose the first of the two options the issue laid out: refuse outright rather than count truncated-but-sent as success. Moved the size check to the front of
txring_put(), before it waits for a ring slot, and changed it to return-1witherrnoset toEMSGSIZEinstead of clamping and continuing. Nothing is written into the ring and no frame the kernel could transmit ever gets queued, so "failed" now means what it says.sendpacket()'s existing generic error handling (src/common/sendpacket.c) already treatsEMSGSIZElike any other hard failure - sets a descriptive message, counts it, doesn't retry (retries are reserved forEAGAIN/ENOBUFS, which would be pointless here: a packet that doesn't fit this MTU never will) - so no caller-side changes were needed.Fragmenting instead of refusing is the
/* TODO Fragment packet */this replaces, and is still not attempted here - refusing is the smaller, no-worse-than-before change; actually fragmenting is a separate feature, not a bug fix.Verification
Reproduced the issue's own repro (MTU 1280,
test.pcapup to 1514 bytes) before and after:Packets that fit the MTU are no longer stuck in the ring behind ones that don't, so "Successful packets" now reports the packets that actually went out.
test/unit/test_txring.c's oversized-packet case updated to assert the refusal (-1/EMSGSIZE, frame leftTP_STATUS_AVAILABLE) instead of the old clamped-length/partial-copy behavior: 164/164 assertions.sudo make tcpreplay(full replay group): 19/19.asanjob config (ASan+UBSan, alignment included since [Bug] misaligned struct access is systemic in the packet parsers (15 sites) #1104): 78/78, 0 sanitizer reports.🤖 Generated with Claude Code
https://claude.ai/code/session_01NBmWiWg46r8BLbdwozKo6v