Testing TCP/UDP traffic with iperf
The latency vs bandwidth article worked through why a link's theoretical capacity and what a single connection actually achieves can be two very different numbers — but that article's numbers came from a description, not a measurement you ran yourself. iperf3 is the standard tool for generating exactly that measurement: real, sustained traffic between two hosts, over both TCP and UDP, with hard numbers at the end.
Goal and end result
By the end of this lab, you'll have measured real throughput between two hosts over TCP, then done the same over UDP at a fixed rate, and compared what each protocol reports when the network can't keep up — TCP hides it behind retransmissions and a shrinking window; UDP reports it plainly, as dropped datagrams.
Requirements
- Two hosts or containers that can reach each other over the network you want to measure — the virtual home network lab's Docker setup works for confirming the tool's behavior, though the numbers it reports there reflect a virtual bridge's throughput, not a real physical link's.
iperf3installed on both.
Safety note
iperf3 deliberately generates as much traffic as it can (or a rate you specify) for the duration of the test — running an unthrottled test against a production link, a shared office network, or any connection other people depend on can saturate it for everyone using it at the time. Confine testing to a link you control or a maintenance window on one you don't.
Step 1: start the server side
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
iperf3 runs as a client/server pair — the server side listens and waits, printing nothing further until a client actually connects, at which point it starts reporting the test as it runs.
Step 2: run a basic TCP test
Connecting to host <server-address>, port 5201
[ 5] local 192.168.100.3 port 43874 connected to 192.168.100.2 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 7.22 GBytes 61.9 Gbits/sec 22 858 KBytes
[ 5] 1.00-2.00 sec 6.80 GBytes 58.4 Gbits/sec 0 865 KBytes
[ 5] 2.00-3.00 sec 7.49 GBytes 64.3 Gbits/sec 0 882 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-3.00 sec 22.4 GBytes 64.2 Gbits/sec 22 sender
[ 5] 0.00-3.00 sec 22.4 GBytes 64.2 Gbits/sec receiver
Two columns matter beyond the headline Bitrate: Retr is the retransmission count for that interval — the exact mechanism TCP's reliability guarantee depends on, made visible as a number instead of an abstract property — and Cwnd, the current congestion window, the same value congestion control already explained as the mechanism deciding how much unacknowledged data TCP allows in flight at once. The number above the multi-gigabit range is specific to this test running over a virtual container bridge rather than a real network link — a real home connection, a real data-center link, or a real WAN path will show throughput bounded by that link's actual capacity, not by how fast two processes on the same physical machine can shuttle memory back and forth.
Step 3: run the equivalent UDP test
Connecting to host <server-address>, port 5201
[ 5] local 192.168.100.3 port 34802 connected to 192.168.100.2 port 5201
[ ID] Interval Transfer Bitrate Total Datagrams
[ 5] 0.00-1.00 sec 11.9 MBytes 99.9 Mbits/sec 8625
[ 5] 1.00-2.00 sec 11.9 MBytes 100 Mbits/sec 8633
[ 5] 2.00-3.00 sec 11.9 MBytes 100 Mbits/sec 8632
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-3.00 sec 35.8 MBytes 100 Mbits/sec 0.000 ms 0/25890 (0%) sender
[ 5] 0.00-3.00 sec 35.8 MBytes 100 Mbits/sec 0.001 ms 0/25890 (0%) receiver
-u switches to UDP, and -b 100M is required for it in a way it isn't for TCP: UDP has no built-in flow control or congestion control at all — the sockets article already covered this from the code side — so without an explicit rate limit, iperf3 would simply fire datagrams as fast as it can generate them, telling you nothing useful about a specific target rate. The summary line reports Jitter — variation in arrival timing between datagrams, the exact property UDP's use cases already named as the reason live media cares more about consistent timing than about any single datagram's fate — and Lost/Total Datagrams, a direct, unhidden count of what didn't arrive.
Reading the difference in how each protocol reports the same underlying problem
Push both tests past what the actual link can sustain, and the two report the same kind of shortfall in structurally different ways. Force the UDP test to demand more than the link can carry:
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-3.00 sec 340 MBytes 951 Mbits/sec 1.842 ms 4213/246812 (1.7%) sender
UDP reports the shortfall plainly: a Lost/Total Datagrams figure showing exactly how much didn't make it, with no attempt to compensate. A TCP test pushed past the same ceiling never reports a number that looks like loss at all — instead, Retr climbs and Cwnd shrinks, because TCP's congestion control is actively reacting to the same underlying packet loss by slowing itself down and resending, which is exactly why the Bitrate column alone, without also watching Retr, can make a genuinely struggling TCP connection look merely slower rather than actively losing and recovering data underneath.
UDP shows you the loss directly; TCP shows you the consequence of hiding it — a lower rate and a rising retransmission count instead of a lost-packet count. Both tests are measuring the identical physical reality; the two protocols just report it through entirely different windows.
Troubleshooting
- The client reports
iperf3: error - unable to connect to server. Confirm the server process is actually running (iperf3 -sprints nothing further once it's listening, which can look like it silently failed) and that nothing blocks port 5201 between the two hosts. - TCP throughput looks unexpectedly low on a link with plenty of bandwidth. Check
Retrspecifically — a nonzero, climbing retransmission count with otherwise-fine hardware often points at a lossy link segment somewhere in the path, not a bandwidth ceiling at all. - UDP
Lost/Total Datagramsshows loss even at a modest rate on a link that should easily handle it. This can indicate a receiving host's own network stack or CPU falling behind processing incoming datagrams rather than a problem with the link itself — try the same rate against a different, less loaded receiving host to isolate which side the bottleneck is actually on.
Cleanup
Stop the server side with Ctrl-C; nothing else needs to be undone, since both tests only generated transient traffic between the two hosts.
Evaluation criterion
You've completed this lab correctly if you can show a TCP test's Retr and Cwnd columns and a UDP test's Lost/Total Datagrams column from your own run, and explain in one sentence why forcing a UDP test past the link's capacity produces a nonzero loss percentage while an equivalently overloaded TCP test instead shows a falling bitrate and rising retransmissions.
This lab measured raw transport-layer throughput between two hosts you controlled directly. The final lab in this course applies everything measured and built across this module to something closer to a real deployment: several small services, each with its own container, talking to each other the way an actual application's backend does.