Skip to content

Fix client file descriptor leaks#2034

Open
hamaaja wants to merge 3 commits into
esnet:masterfrom
hamaaja:client-fd-leak-fix
Open

Fix client file descriptor leaks#2034
hamaaja wants to merge 3 commits into
esnet: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.

@bmah888
Copy link
Copy Markdown
Contributor

bmah888 commented May 15, 2026

I'm start to play with this bug and fixes. Just FYI to invoke the server you can just run src/iperf3 -s rather than explicitly setting LD_LIBRARY_PATH, etc. as you're doing. libtools creates src/iperf3 as a shell script that does this stuff for you.

Copy link
Copy Markdown
Contributor

@bmah888 bmah888 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for the PR!

Just one comment in the code about another place where you might want to make your changes to close the buffer file descriptor.

Other than that, I think we're looking good on this, I confirm that your patch seems to fix the file descriptor leaks that valgrind found.

Comment thread src/iperf_api.c Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this line of code also change to a check for a valid value of sp->buffer_fd, closing the file descriptor, and then setting sp->buffer_fd to -1, the same as you did earlier in the file?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking sp->buffer_fd isn't necessary here because the descriptor is opened in the same function and if we get to this line the value will be valid.

But I did notice that error handling code in the function before this line does not close sp->buffer_fd. sp->diskfile_fd will also leak if iperf_init_stream() call fails. I have a commit fixing these and will push it shortly.

Also, I'd like to suggest moving error cleanups to the and of the function in error exit labels and jumping there using goto. I'll do this in a separate commit and add it to this PR. If it's not wanted we can just revert it or I can force push the fix branch without it.

@hamaaja
Copy link
Copy Markdown
Author

hamaaja commented May 17, 2026

I pushed two additional commits addressing leaks in iperf_new_stream() error handling and moving cleanups to the end of the iperf_new_stream() function. These changes also take care of previously missed sp->diskfile_fd leak in the error handling.

@hamaaja hamaaja requested a review from bmah888 May 17, 2026 13:55
@bmah888 bmah888 self-assigned this May 18, 2026
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