Skip to content

fix: txring_put() refuses over-MTU packets instead of truncating them (#1108) - #1113

Merged
fklassen merged 1 commit into
4.6.1-beta1from
fix-txring-refuse-oversized
Jul 29, 2026
Merged

fix: txring_put() refuses over-MTU packets instead of truncating them (#1108)#1113
fklassen merged 1 commit into
4.6.1-beta1from
fix-txring-refuse-oversized

Conversation

@fklassen

Copy link
Copy Markdown
Member

Fixes #1108.

txring_put() used to copy in only the first max_len bytes 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 - 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.

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 -1 with errno set to EMSGSIZE instead 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 treats EMSGSIZE like any other hard failure - sets a descriptive message, counts it, doesn't 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 /* 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.pcap up to 1514 bytes) before and after:

                         before        after
Truncated packets:          32            0
Successful packets:          0            3

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 left TP_STATUS_AVAILABLE) instead of the old clamped-length/partial-copy behavior: 164/164 assertions.
  • sudo make tcpreplay (full replay group): 19/19.
  • CI's exact asan job config (ASan+UBSan, alignment included since [Bug] misaligned struct access is systemic in the packet parsers (15 sites) #1104): 78/78, 0 sanitizer reports.
  • Plain non-sanitizer build and CMake: both 78/78.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NBmWiWg46r8BLbdwozKo6v

…#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
@fklassen
fklassen merged commit fb4f2f8 into 4.6.1-beta1 Jul 29, 2026
6 checks passed
@fklassen
fklassen deleted the fix-txring-refuse-oversized branch July 29, 2026 13:28
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.

2 participants