Context
When the peer is an older iperf3 that does not exchange the omitted_errors/omitted_packets stream keys (≤3.12 — the keys and the backward-compat acceptance were added later), iperf_get_results substitutes omit baselines locally (src/iperf_api.c, the j_omitted_packets == NULL arms around lines 2914–2950 in 3.21 @ d39cf41). The substituted values make the rendered UDP loss figures incorrect and self-inconsistent in three ways, all reproducible with a stock 3.21 client against a stock 3.12 server on loopback.
Repro (loss forced via a small receive window):
# 3.12 server
iperf-3.12/src/iperf3 -s -1
# 3.21 client, three variants
iperf-3.21/src/iperf3 -c 127.0.0.1 -u -b 2G -l 1200 -w 8K -t 2 -J # (a)
iperf-3.21/src/iperf3 -c 127.0.0.1 -u -b 2G -l 1200 -w 8K -t 2 -O 1 -J # (b)
iperf-3.21/src/iperf3 -c 127.0.0.1 -u -b 2G -l 1200 -w 8K -t 2 -R -J # (c)
Symptom 1 — real loss reported as zero (no -O)
In case (a) the client renders lost_packets: 0 / lost_percent: 0 (observed: 0/417713) despite heavy real loss (the same setup with -O 1 or -R shows hundreds to thousands of lost packets). Cause: the sender arm's else-branch assigns sp->omitted_cnt_error = sp->cnt_error; when there is no omit window, so the render sites' cnt_error - omitted_cnt_error nets the entire count away. Since the old peer's exchanged counts are gross and there is no omit window, the correct baseline here is 0.
Symptom 2 — the same document disagrees with itself by one (-O)
In case (b) the stream object and the sum object differ by exactly 1 (observed: stream lost_packets: 2852, sum lost_packets: 2851). Cause: with an omit window, the sender arm stores the -1 "unknown" sentinel in omitted_cnt_error; the per-stream render subtracts it unconditionally (cnt_error - (-1) = count+1, ~line 4312) while the sum accumulation skips it (if (sp->omitted_cnt_error > -1), lines 4246–4248).
Symptom 3 — lost_percent: 0 beside a nonzero lost_packets (-R)
In case (c) the client's stream object renders e.g. lost_packets: 643, packets: 417573, lost_percent: 0 while the sum shows the true percentage (0.154). Cause: the receiver arm assigns sp->peer_omitted_packet_count = sp->peer_packet_count; (everything omitted), and the stream lost_percent denominator uses sender_packet_count - sender_omitted_packet_count (= 0, lines 4288–4292) even though the adjacent packets field is computed with a different subtraction and stays nonzero.
Suggested direction
For the no-omit case the baselines should be 0 (the exchanged gross counts are already the net figures). With -O, the sender side's own omitted-sent count is a usable estimate for the peer's omitted packets (the code already does this), the receiver side's own omitted-received count is a better estimate than all-omitted, and the error split is genuinely unknown — but the sentinel should not leak into the subtraction on one surface while being skipped on another.
Found while validating riperf3's output compatibility against 3.21/3.12 pairs; happy to provide the full JSON captures.
Context
When the peer is an older iperf3 that does not exchange the
omitted_errors/omitted_packetsstream keys (≤3.12 — the keys and the backward-compat acceptance were added later),iperf_get_resultssubstitutes omit baselines locally (src/iperf_api.c, thej_omitted_packets == NULLarms around lines 2914–2950 in 3.21 @ d39cf41). The substituted values make the rendered UDP loss figures incorrect and self-inconsistent in three ways, all reproducible with a stock 3.21 client against a stock 3.12 server on loopback.Repro (loss forced via a small receive window):
Symptom 1 — real loss reported as zero (no
-O)In case (a) the client renders
lost_packets: 0/lost_percent: 0(observed:0/417713) despite heavy real loss (the same setup with-O 1or-Rshows hundreds to thousands of lost packets). Cause: the sender arm's else-branch assignssp->omitted_cnt_error = sp->cnt_error;when there is no omit window, so the render sites'cnt_error - omitted_cnt_errornets the entire count away. Since the old peer's exchanged counts are gross and there is no omit window, the correct baseline here is 0.Symptom 2 — the same document disagrees with itself by one (
-O)In case (b) the stream object and the sum object differ by exactly 1 (observed: stream
lost_packets: 2852, sumlost_packets: 2851). Cause: with an omit window, the sender arm stores the-1"unknown" sentinel inomitted_cnt_error; the per-stream render subtracts it unconditionally (cnt_error - (-1)= count+1, ~line 4312) while the sum accumulation skips it (if (sp->omitted_cnt_error > -1), lines 4246–4248).Symptom 3 —
lost_percent: 0beside a nonzerolost_packets(-R)In case (c) the client's stream object renders e.g.
lost_packets: 643, packets: 417573, lost_percent: 0while the sum shows the true percentage (0.154). Cause: the receiver arm assignssp->peer_omitted_packet_count = sp->peer_packet_count;(everything omitted), and the streamlost_percentdenominator usessender_packet_count - sender_omitted_packet_count(= 0, lines 4288–4292) even though the adjacentpacketsfield is computed with a different subtraction and stays nonzero.Suggested direction
For the no-omit case the baselines should be 0 (the exchanged gross counts are already the net figures). With
-O, the sender side's own omitted-sent count is a usable estimate for the peer's omitted packets (the code already does this), the receiver side's own omitted-received count is a better estimate than all-omitted, and the error split is genuinely unknown — but the sentinel should not leak into the subtraction on one surface while being skipped on another.Found while validating riperf3's output compatibility against 3.21/3.12 pairs; happy to provide the full JSON captures.