Skip to content

Fix client file descriptor leaks#2034

Open
hamaaja wants to merge 1 commit intoesnet:masterfrom
hamaaja:client-fd-leak-fix
Open

Fix client file descriptor leaks#2034
hamaaja wants to merge 1 commit intoesnet:masterfrom
hamaaja:client-fd-leak-fix

Conversation

@hamaaja
Copy link
Copy Markdown

@hamaaja hamaaja commented May 5, 2026

PLEASE NOTE the following text from the iperf3 license. Submitting a
pull request to the iperf3 repository constitutes "[making]
Enhancements available...publicly":

You are under no obligation whatsoever to provide any bug fixes, patches, or
upgrades to the features, functionality or performance of the source code
("Enhancements") to anyone; however, if you choose to make your Enhancements
available either publicly, or directly to Lawrence Berkeley National
Laboratory, without imposing a separate written license agreement for such
Enhancements, then you hereby grant the following license: a non-exclusive,
royalty-free perpetual license to install, use, modify, prepare derivative
works, incorporate into other computer software, distribute, and sublicense
such enhancements or derivative works thereof, in binary and source code form.

The complete iperf3 license is available in the LICENSE file in the
top directory of the iperf3 source tree.

  • Version of iperf3 (or development branch, such as master or
    3.1-STABLE) to which this pull request applies: master and at least releases 3.20 and 3.21

  • Issues fixed (if any): -

  • Brief description of code changes (suitable for use as a commit message):

Close stream buffer fd in iperf_client_end(). Close operation is protected by checking if the fd is valid. This prevents double close in case there's a code path calling iperf_free_stream() before iperf_client_end().

Protect stream buffer fd close in iperf_free_stream() with fd validity check. This prevent double close in normal test success. Double close is probably fine for close() call but valgrind will nag about it.

Close /dev/urandom file in readentropy() after reading it. This prevents fd leaks in cases where libiperf is dlopen()'ed, a test is executed and the lib is dlclose()'ed repeatedly.

Background

We're using iperf as a dynamically loaded library (via dart's foreign function interface) in a long running process. When not in use the library is unloaded. With enough repeats client will run out of file descriptors due to leaks. In normal success case where iperf client ends after given duration (-t option) the /dev/urandom file in readentropy() leaks. This happens because we "forget" the static file in library unloading. "Network unreachable" errors also cause this leak.

Another leak happens if the connection to server is lost during the test: The stream buffer fd is not closed in this case.

There may be other leaks as we didn't do extensive testing but these are the ones we're currently running into.

Reproducers

Luckily these leaks are also reproducible in normal command line environment. We're using valgrind to detect fd leaks and running iperf in Fedora 44 KDE.

Version (the current master) and building:

$ git log -1 --pretty=oneline 
00e5d5ebb17f9df0f90966d52933c740a7ff8411 (HEAD -> master, origin/master, origin/HEAD, iperf/master, iperf/HEAD) Merge pull request #2028 from rpigott/stray-cr

$ ./configure && make -j$(nproc)

Server command line:

# Use built libs instead of system ones
$ export LD_LIBRARY_PATH=$(pwd)/src/.libs
$ src/.libs/iperf3 -s

Client command line and valgrind output when test finishes after given duration:

# Use built libs instead of system ones
$ export LD_LIBRARY_PATH=$(pwd)/src/.libs
$ valgrind --track-fds=yes src/.libs/iperf3 -c 127.0.0.1 -t 5
==100941== Memcheck, a memory error detector
==100941== Copyright (C) 2002-2026, and GNU GPL'd, by Julian Seward et al.
==100941== Using Valgrind-3.27.0 and LibVEX; rerun with -h for copyright info
==100941== Command: src/.libs/iperf3 -c 127.0.0.1 -t 5
==100941== 
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 37174 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   789 MBytes  6.60 Gbits/sec    0   1.12 MBytes       
[  5]   1.00-2.00   sec   774 MBytes  6.51 Gbits/sec    0   1.12 MBytes       
[  5]   2.00-3.00   sec   826 MBytes  6.93 Gbits/sec    0   1.12 MBytes       
[  5]   3.00-4.00   sec   796 MBytes  6.68 Gbits/sec    0   1.12 MBytes       
[  5]   4.00-5.03   sec   818 MBytes  6.68 Gbits/sec    0   1.12 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-5.03   sec  3.91 GBytes  6.68 Gbits/sec    0            sender
[  5]   0.00-5.04   sec  3.91 GBytes  6.67 Gbits/sec                  receiver

iperf Done.
==100941== 
==100941== FILE DESCRIPTORS: 4 open (3 inherited) at exit.
==100941== Open file descriptor 3: /dev/urandom
==100941==    at 0x4AAF056: open (open64.c:41)
==100941==    by 0x4A2F0B2: _IO_file_open (fileops.c:222)
==100941==    by 0x4A2F26A: _IO_file_fopen@@GLIBC_2.2.5 (fileops.c:315)
==100941==    by 0x4A22750: __fopen_internal (iofopen.c:75)
==100941==    by 0x487369E: readentropy (iperf_util.c:66)
==100941==    by 0x487369E: readentropy (iperf_util.c:58)
==100941==    by 0x487378D: make_cookie (iperf_util.c:120)
==100941==    by 0x486DB29: iperf_connect.part.0 (iperf_client_api.c:434)
==100941==    by 0x486E7D8: iperf_run_client (iperf_client_api.c:634)
==100941==    by 0x400828: run (main.c:201)
==100941==    by 0x400526: main (main.c:124)
==100941== 
==100941== 
==100941== HEAP SUMMARY:
==100941==     in use at exit: 664 bytes in 4 blocks
==100941==   total heap usage: 135 allocs, 131 frees, 22,633 bytes allocated
==100941== 
==100941== LEAK SUMMARY:
==100941==    definitely lost: 0 bytes in 0 blocks
==100941==    indirectly lost: 0 bytes in 0 blocks
==100941==      possibly lost: 0 bytes in 0 blocks
==100941==    still reachable: 664 bytes in 4 blocks
==100941==         suppressed: 0 bytes in 0 blocks
==100941== Rerun with --leak-check=full to see details of leaked memory
==100941== 
==100941== For lists of detected and suppressed errors, rerun with: -s
==100941== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Client command line and valgrind output when test stops because the server is killed (with ctrl-c), equal to our "connection to server is lost" case:

# Use built libs instead of system ones
$ export LD_LIBRARY_PATH=$(pwd)/src/.libs
$ valgrind --track-fds=yes src/.libs/iperf3 -c 127.0.0.1 -t 5
==102452== Memcheck, a memory error detector
==102452== Copyright (C) 2002-2026, and GNU GPL'd, by Julian Seward et al.
==102452== Using Valgrind-3.27.0 and LibVEX; rerun with -h for copyright info
==102452== Command: src/.libs/iperf3 -c 127.0.0.1 -t 5
==102452== 
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 52164 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   789 MBytes  6.60 Gbits/sec    0   1.25 MBytes       
[  5]   1.00-2.00   sec   805 MBytes  6.76 Gbits/sec    0   1.25 MBytes       
[  5]   1.00-2.00   sec   805 MBytes  6.76 Gbits/sec    0   1.25 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-2.00   sec  1.76 GBytes  7.54 Gbits/sec    0            sender
[  5]   0.00-2.00   sec  0.00 Bytes  0.00 bits/sec                  receiver
iperf3: error - unable to write to stream socket: Transport endpoint is not connected
==102452== 
==102452== FILE DESCRIPTORS: 5 open (3 inherited) at exit.
==102452== Open file descriptor 6: /tmp/iperf3.MRKhdt
==102452==    at 0x4AAF056: open (open64.c:41)
==102452==    by 0x4A02A3D: try_tempname_len (tempname.c:264)
==102452==    by 0x4A02A3D: gen_tempname_len (tempname.c:187)
==102452==    by 0x4A02A3D: __gen_tempname (tempname.c:282)
==102452==    by 0x48641CC: iperf_new_stream (iperf_api.c:4833)
==102452==    by 0x486DF54: iperf_create_streams (iperf_client_api.c:168)
==102452==    by 0x486E435: iperf_handle_message_client (iperf_client_api.c:349)
==102452==    by 0x486EE80: iperf_run_client (iperf_client_api.c:717)
==102452==    by 0x400828: run (main.c:201)
==102452==    by 0x400526: main (main.c:124)
==102452== 
==102452== Open file descriptor 3: /dev/urandom
==102452==    at 0x4AAF056: open (open64.c:41)
==102452==    by 0x4A2F0B2: _IO_file_open (fileops.c:222)
==102452==    by 0x4A2F26A: _IO_file_fopen@@GLIBC_2.2.5 (fileops.c:315)
==102452==    by 0x4A22750: __fopen_internal (iofopen.c:75)
==102452==    by 0x487369E: readentropy (iperf_util.c:66)
==102452==    by 0x487369E: readentropy (iperf_util.c:58)
==102452==    by 0x487378D: make_cookie (iperf_util.c:120)
==102452==    by 0x486DB29: iperf_connect.part.0 (iperf_client_api.c:434)
==102452==    by 0x486E7D8: iperf_run_client (iperf_client_api.c:634)
==102452==    by 0x400828: run (main.c:201)
==102452==    by 0x400526: main (main.c:124)
==102452== 
==102452== 
==102452== HEAP SUMMARY:
==102452==     in use at exit: 3,600 bytes in 14 blocks
==102452==   total heap usage: 52 allocs, 38 frees, 12,429 bytes allocated
==102452== 
==102452== LEAK SUMMARY:
==102452==    definitely lost: 0 bytes in 0 blocks
==102452==    indirectly lost: 0 bytes in 0 blocks
==102452==      possibly lost: 0 bytes in 0 blocks
==102452==    still reachable: 3,600 bytes in 14 blocks
==102452==         suppressed: 0 bytes in 0 blocks
==102452== Rerun with --leak-check=full to see details of leaked memory
==102452== 
==102452== For lists of detected and suppressed errors, rerun with: -s
==102452== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

These client leaks get fixed with this PR.

Close stream buffer fd in `iperf_client_end()`. Close operation is
protected by checking if the fd is valid. This prevents double close in
case there's a code path calling iperf_free_stream() before
iperf_client_end().

Protect stream buffer fd close in `iperf_free_stream()` with fd validity
check. This prevent double close in normal test success. Double close
is probably fine for close() call but valgrind will nag about it.

Close /dev/urandom file in `readentropy()` after reading it. This
prevents fd leaks in cases where libiperf is dlopen()'ed, a test is
executed and the lib is dlclose()'ed repeatedly.
@bmah888
Copy link
Copy Markdown
Contributor

bmah888 commented May 9, 2026

Thanks for the PR! We'll take a look at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants