Skip to content

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.
  • iperf3 installed 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

iperf3 -s
-----------------------------------------------------------
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

iperf3 -c <server-address> -t 3
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

iperf3 -c <server-address> -u -b 100M -t 3
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:

iperf3 -c <server-address> -u -b 1000M -t 3
[ 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 -s prints 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 Retr specifically — 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 Datagrams shows 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.

Sources