pof: fix conditional delay buffer stalling under sustained reordered traffic - #1
Open
howhangliu wants to merge 4 commits into
Open
pof: fix conditional delay buffer stalling under sustained reordered traffic#1howhangliu wants to merge 4 commits into
howhangliu wants to merge 4 commits into
Conversation
Three further fixes to the POF worker found under sustained traffic
(100 pkt/s FRER stream with reordering between member paths):
- When the earliest forward deadline had already passed at re-arm
time, the poll timeout was set to take_any_time (seconds) instead
of firing immediately. Under continuous traffic every new arrival
re-armed it again, so held packets were never released: the
conditional delay buffer filled up and all subsequent packets were
dropped ('buffer is full, dropping new packet'). Arm a zero
timeout when the deadline is in the past.
- The eventfd is a counter, so concurrent notifications sum up
(e.g. two IN_ORDER writes read back as 2 == OUT_OF_ORDER) and
bit-testing the sum misclassifies events. Attempt a forward on any
positive event; pof_try_forward() already only forwards when the
head is next in order.
- Guard pof_try_forward() against an empty queue: it dereferenced
q_head unconditionally, which can be NULL when an event is read
after a timeout already drained the queue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the network-namespace testbed used to find and verify the two POF fixes in this branch. It is self-contained (netns + veth + tc netem, no external hardware) so the failure can be reproduced from a clean checkout. Topology: a periodic talker feeds a FRER stream that is replicated over two member paths with independent exponential delay, then recovered and reordered by a second DNT instance (nxp1.ini -> nxp2.ini). The reordering between member paths under sustained 100 pkt/s traffic is what drove the POF conditional delay buffer into the 'buffer is full, dropping new packet' state before the fix. Contents: env_frer_aoi.sh namespace/veth/netem builder (source as root) verify_setup.sh checks the environment before a run talker.py periodic source listener.py receiver, records per-packet peak age gen_expo_dist.py netem exponential delay table run_point.sh one (D,H) measurement point sweep.sh sweeps D, R repetitions per point aggregate_results.py sweep output -> per-(D,H) means with 95% CIs README.md full walkthrough, incl. the repro procedure Generated result files are gitignored; finished campaigns can be archived under dnt_testbed/results_archive/ if wanted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a case that fails on the unfixed tree and passes with the POF fixes in this branch (6/7 vs 7/7). The existing cases cannot reach this bug: each sends 100 packets into a BufferSize=200 queue, so the conditional delay buffer can never fill, and MaxDelay=520 sits far above the 30ms path delay, so a forward deadline essentially never expires while packets are held. dntbr1_stall.ini inverts both: MaxDelay=16 is below the path delay so deadlines do expire, BufferSize=32 is well under the packet count, and TakeAnyTime=5000 is large enough that take-any cannot mask the stall within the run. The test drives a single member path and briefly downs it mid-ping, leaving a sequence gap that can never be filled, so POF has to release the held head on its deadline. The assertion is on DNT's own 'buffer is full, dropping new packet' warning rather than on ping delivery: the deliberate link-down costs packets in both the healthy and the stalled case, leaving the delivered counts only a few packets apart (287 vs 293 observed), which is not a usable signal. The warning is logged at the default level, so the test needs no extra verbosity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Summary
Fixes three defects in the POF (Packet Ordering Function) worker that
cause the conditional delay buffer to fill permanently and drop all
subsequent packets under sustained, reordered traffic.
Adds a
test/pof/pof.pycase that fails onmainand passes here, plusthe network-namespace testbed the bug was originally found on.
The bug
Observed with a FRER stream at 100 pkt/s replicated over two member
paths with independent delay (reordering between the members). After a
short time DNT logs
and never recovers — every subsequent packet on the stream is dropped.
1. Expired deadline re-arms to
take_any_timeinstead of firingIn
pof_thread(), the poll timeout was computed as:The
elsebranch conflates two different cases: no deadline at alland deadline already in the past. When the earliest forward deadline
had already passed, the thread armed
take_any_time(seconds) ratherthan firing immediately. Under continuous traffic each new arrival
re-armed it again, so held packets were never released, the buffer
filled, and everything after that was dropped.
Split the cases:
NULLdeadline keepstake_any_time, a deadline inthe past arms a zero timeout.
2.
eventfdcounter values tested as bit flagseventfdaccumulates: two concurrentPOF_IN_ORDER_PKT(1) writes readback as
2, which isPOF_OUT_OF_ORDER_PKT. Testing bits of the sumtherefore misclassifies events and drops in-order wakeups.
Attempt a forward on any positive event;
pof_try_forward()alreadyonly forwards when the head is next in order, so this is harmless.
3.
get_next_deadline()did not select the earliest deadlineThe scan used
timespeccmp(..., !=)against a moving target and startedat
q_head, so it ended up tracking the last differing element ratherthan the minimum, and left
next_to_forwardunset in the single-elementcase. Compare with
<and seed the scan properly.Additionally,
pof_try_forward()dereferencedpof->q_headunconditionally; it can be
NULLwhen an event is read after a timeoutalready drained the queue. Guarded.
Reproduction
test/pof/pof.pygains a case that fails onmainand passes with thisbranch:
Why the existing cases miss it
MaxDelayTakeAnyTimeBufferSizeTwo independent reasons the current suite cannot reach the bug: each case
sends 100 packets into a
BufferSize = 200queue, sobuffer is fullisarithmetically unreachable; and
MaxDelay = 520 mssits far above the30 ms path delay, so a forward deadline essentially never expires while
packets are held.
dntbr1_stall.iniinverts both, and the case drives a single member pathand briefly downs it mid-ping, leaving a sequence gap that can never be
filled - so POF must release the held head on its deadline.
The assertion is on DNT's own
buffer is full, dropping new packetwarning, not on ping delivery. The deliberate link-down costs packets in
both the healthy and the stalled case, so delivered counts land only a few
packets apart (287 vs 293 observed) - not a usable signal. The warning is
logged at the default level, so no extra verbosity is needed.
The original scenario
dnt_testbed/is where this was first hit: a FRER stream replicated overtwo member paths with independent exponential delay, recovered and
reordered by a second DNT instance. It is self-contained (network
namespaces, veth,
tc netem, no external hardware);dnt_testbed/README.mdhas the walkthrough:Notes for review
gitignored.
dnt_testbed/README.mdis written around a specificstudy (a delay/history-length sweep), so parts of it are more
detailed than a pure regression harness would need — happy to trim it
down, split it into a separate PR, or move it under
test/if youwould rather keep it out of the tree root.
python3-scapyandpython3-regexin additionto what
.gitlab-ci.ymlalready installs.counter fix and the
q_headNULL guard have no direct coverage here.The NULL deref in particular is only reachable with
POF:PACKETlogging enabled, since
log_packet()guards evaluation of its ownarguments - at the default level the offending dereference is never
executed.