Repository navigation
netfix-receive-drain-skips-empty-datagrams-and-stops-at-a-cap: an empty datagram is skipped and a drain stops at its cap - #194
Merged
Conversation
…ceive result is classified in one function By: Rowan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sts for the empty-datagram drain stop and the missing cap By: Rowan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ty datagram is skipped and a drain stops at its cap By: Rowan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rowan-claude
merged commit Oct 14, 2026
7cd5f51
into
sprint/netcode-audit-fixes
18 of 19 checks passed
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.
Card:
netfix-receive-drain-skips-empty-datagrams-and-stops-at-a-cap(stream netcode-audit-fixes, blocker). Work card.w1, generation 1, epoch 20.The defect
A zero-length UDP datagram is legal, and
recvfromreturns 0 for it. netcode read 0 as "the socket is drained", so one empty datagram ended the receive loop for that update, on both the server and the client. The socket loops were alsowhile ( 1 )with no per-update cap. Anyone who could reach the port could send a few hundred empty datagrams a second (a few KB/s, no authentication needed) and keep the kernel queue growing until it filled, and then every client's packets were dropped.The fix (netcode.c only, no wire change)
netcode_classify_receive_result( result, error, would_block )decides what onerecvfromresult means: a positive result is the byte count, 0 means would-block (drained), and -1 means skip this datagram and keep draining. -1 covers a zero-length datagram and any other recvfrom error.netcode_socket_error_would_blockholds the per-platform would-block test (EAGAIN or EWOULDBLOCK; WSAEWOULDBLOCK on Windows).if ( packet_bytes == 0 ) break; if ( packet_bytes < 0 ) continue;. They count every iteration and stop atNETCODE_SERVER_MAX_RECEIVE_PACKETSon the server andNETCODE_CLIENT_MAX_RECEIVE_PACKETSon the client, the same caps the network simulator path already used.receive_packet_overridekeeps its meaning (0 = nothing more). It may now also return -1 for "skip".Commits (one per step)
d0c8f7aextracts the classifier with no behaviour change. The full suite stays green.d8ad227adds the red tests:test_receive_result_classification,test_receive_drain_stops_at_cap(server and client), andtest_receive_drain_skips_empty_datagram(a pin). They use the receive override with a scripted queue: no socket, and time is passed as an argument.95b2b91is the fix.Red at d8ad227 (Debug, hetzner1)
The same tree with the classification test unregistered, to show the cap test red:
Green at 95b2b91 (hetzner1)
Debug
./build/bin/test, exit 0:ASan+UBSan (
-DNETCODE_SANITIZE=ON)./build-asan/bin/test, exit 0, 0 sanitizer lines:Notes for the reader
receive_packet_overridefields,// returns bytes received, 0 when there is nothing more to read, or -1 to skip this datagram and keep reading.netcode_server_read_and_process_packet, because that function returns at once forpacket_bytes <= 1. Thecontinuebefore the call is what guarantees it.netcode_sockaddr_to_addressfailing after a successful read still returns 0. It is asserted unreachable and was not touched.Worked by Rowan (Opus lane)
🤖 Generated with Claude Code