Disclosure: I develop an iPerf3 app for Apple platforms and Android. The examples below use the upstream iperf3 CLI and apply to compatible clients and servers.
Suppose iperf3 prints this line:
iperf3: error - unable to write to the control socket:Bad file descriptor
Keep the whole line. Bad file descriptor comes from your operating system. It does not identify the operation that failed. The text before it names the iperf3 stage.
In this case, iperf3 tried to write to its control socket. The operating system rejected the descriptor passed to that write with EBADF.
One line, two sources
iperf3 builds many error strings from two parts:
-
iperf3 operation:
unable to write to the control socket -
OS errno:
Bad file descriptor
The iperf3 message answers: What was the program doing?
The errno text answers: Why did that system call fail?
The implementation is visible in src/iperf_error.c. For IECTRLWRITE, iperf3 starts with unable to write to the control socket. It then appends strerror(errno) when the failing call supplied an errno.
Some messages have no appended errno. control socket has closed unexpectedly is one example. iperf3 uses it after a control read returns no state byte. On the client, an empty read points to end-of-file from the peer. The server can reach the same message after its timed read returns without data.
What EBADF tells you
Bad file descriptor is the text for EBADF. The process passed an invalid descriptor to a system call.
That establishes one local fact: the descriptor was invalid inside the process that printed the error. It does not tell you why the descriptor reached that state. Possible triggers include cleanup after another failure, a descriptor-lifetime bug, or code running after a connection teardown.
A firewall or the remote peer can start a failure sequence by closing a connection. The final EBADF still describes only the local system call that failed at the end of that sequence.
Similar messages point to different stages
Read the iperf3 part before interpreting the suffix:
| Message | Failed stage | First check |
|---|---|---|
unable to write to the control socket: <errno> |
A write to the control socket failed. | Check the previous state exchange and the appended errno. |
unable to receive cookie at server: <errno> |
The server accepted a data-stream socket but could not read its cookie. | Compare the client log and the server errno. |
select failed: <errno> |
The kernel rejected something in a select() descriptor set. |
Use --debug to find the last completed stage. |
control socket has closed unexpectedly |
A control read returned no state byte. No errno is appended. | Compare both logs at the same timestamp. |
This table explains why searching only for bad file descriptor produces weak answers. The same suffix can appear after several iperf3 operations.
A debugging sequence that keeps the evidence
Start with the process that printed the line. Then collect the matching evidence from the other side.
- Save the complete client and server commands.
- Save the full output from both processes.
- Record
iperf3 --versionon both machines. iperf2 and iperf3 do not interoperate. - Classify the failed stage from the iperf3 message.
- Interpret
errnoonly after you know that stage. - Repeat the test with debug logs:
iperf3 -s --debug >server.log 2>&1
iperf3 -c 192.168.1.10 --debug >client.log 2>&1
Match the last completed state on both sides. If one log stops first, check whether that host slept, the process exited, or the network path changed.
Run a short and a long test when the failure appears late:
iperf3 -c 192.168.1.10 -t 30
iperf3 -c 192.168.1.10 -t 300
A short run that passes and a long run that fails gives you a time-dependent lead. Check idle firewall state, device sleep, Wi-Fi roaming, and process restarts. Treat each as a testable candidate.
If the logs disagree about which side stopped first, capture port 5201 and compare packet timestamps with both logs:
sudo tcpdump -ni any tcp port 5201
Use a real interface name on systems without the any pseudo-interface.
Other suffixes use the same method
The same reading order works for Connection refused, Permission denied, and Address already in use.
For example, unable to connect to server: Connection refused identifies a failed connect attempt. The suffix says the destination rejected that TCP connection. Check the server process, address, and port before changing unrelated socket settings.
The full iperf3 error reference covers busy servers, listener failures, temporary-file permissions, socket buffers, mobile sleep, and long-test failures.
If you want to run the same tests from an iPhone, iPad, or Mac, the iPerf3 app for Apple platforms can act as a client or server.
Top comments (0)