Add Time Flag For The Iperf Server Process
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
masteror3.1-STABLE) to which this pull request applies: master -
Issues fixed (if any): #354, #615
-
Brief description of code changes (suitable for use as a commit message): These changes add the functionality as requested by the linked issues. Specifically, this functionality allows the server to determine the maximum duration of an
iperfmeasurement. The implementation is similar to the--server-bitrate-limitflag, where the measurement is forcibly stopped when an interval has exceeded the server's configured bitrate. In this case, the measurement is forcibly stopped when the duration exceeds the server's configured time, which is the value of--server-time.
Example
From the dropdowns below, we can see that the server has enforced a measurement time of 3 seconds. The client attempts to run the measurement for 4 seconds. When the 3rd second is exceeded, the socket is forcibly closed.
server
./src/iperf3 -s --server-time 3
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
Accepted connection from 127.0.0.1, port 44938
[ 6] local 127.0.0.1 port 5201 connected to 127.0.0.1 port 44946
[ ID] Interval Transfer Bitrate
[ 6] 0.00-1.00 sec 3.08 GBytes 26.5 Gbits/sec (omitted)
[ 6] 1.00-2.00 sec 3.35 GBytes 28.8 Gbits/sec (omitted)
[ 6] 2.00-3.00 sec 3.18 GBytes 27.3 Gbits/sec (omitted)
[ 6] 0.00-1.00 sec 3.20 GBytes 27.5 Gbits/sec
[ 6] 1.00-2.00 sec 3.34 GBytes 28.7 Gbits/sec
iperf3: error - select failed: Bad file descriptor
-----------------------------------------------------------
Server listening on 5201 (test #2)
-----------------------------------------------------------
client
./src/iperf3 --client 127.0.0.1 --port 5201 -t 4 --omit 3
Connecting to host 127.0.0.1, port 5201
[ 5] local 127.0.0.1 port 44946 connected to 127.0.0.1 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 3.08 GBytes 26.5 Gbits/sec 0 1.06 MBytes (omitted)
[ 5] 1.00-2.00 sec 3.35 GBytes 28.8 Gbits/sec 0 1.06 MBytes (omitted)
[ 5] 2.00-3.00 sec 3.15 GBytes 27.1 Gbits/sec 0 1.06 MBytes (omitted)
[ 5] 0.00-1.00 sec 3.20 GBytes 27.5 Gbits/sec 0 1.12 MBytes
[ 5] 1.00-2.00 sec 3.34 GBytes 28.7 Gbits/sec 0 1.25 MBytes
[ 5] 1.00-2.00 sec 3.34 GBytes 28.7 Gbits/sec 0 1.25 MBytes
iperf3: error - control socket has closed unexpectedly
client_json
{
"start": {
"connected": [{
"socket": 5,
"local_host": "127.0.0.1",
"local_port": 53314,
"remote_host": "127.0.0.1",
"remote_port": 5201
}],
"version": "iperf 3.16+",
"system_info": "Linux windows-nexus 5.15.146.1-microsoft-standard-WSL2 #1 SMP Thu Jan 11 04:09:03 UTC 2024 x86_64",
"timestamp": {
"time": "Thu, 18 Apr 2024 04:38:09 GMT",
"timesecs": 1713415089
},
"connecting_to": {
"host": "127.0.0.1",
"port": 5201
},
"cookie": "wtuhwwetnqayseccmqno6dutzjf3zly3op5p",
"tcp_mss_default": 32768,
"target_bitrate": 0,
"fq_rate": 0,
"sock_bufsize": 0,
"sndbuf_actual": 16384,
"rcvbuf_actual": 131072,
"test_start": {
"protocol": "TCP",
"num_streams": 1,
"blksize": 131072,
"omit": 3,
"duration": 4,
"bytes": 0,
"blocks": 0,
"reverse": 0,
"tos": 0,
"target_bitrate": 0,
"bidir": 0,
"fqrate": 0,
"interval": 1
}
},
"intervals": [{
"streams": [{
"socket": 5,
"start": 0,
"end": 1.001066,
"seconds": 1.0010659694671631,
"bytes": 3578265600,
"bits_per_second": 28595642717.968742,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 51,
"rttvar": 5,
"pmtu": 65535,
"omitted": true,
"sender": true
}],
"sum": {
"start": 0,
"end": 1.001066,
"seconds": 1.0010659694671631,
"bytes": 3578265600,
"bits_per_second": 28595642717.968742,
"retransmits": 0,
"omitted": true,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 1.001066,
"end": 2.001046,
"seconds": 0.99997997283935547,
"bytes": 3452698624,
"bits_per_second": 27622142185.078888,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 88,
"rttvar": 67,
"pmtu": 65535,
"omitted": true,
"sender": true
}],
"sum": {
"start": 1.001066,
"end": 2.001046,
"seconds": 0.99997997283935547,
"bytes": 3452698624,
"bits_per_second": 27622142185.078888,
"retransmits": 0,
"omitted": true,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 2.001046,
"end": 3.001049,
"seconds": 1.0000029802322388,
"bytes": 3500015616,
"bits_per_second": 28000041481.373692,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 63,
"rttvar": 17,
"pmtu": 65535,
"omitted": true,
"sender": true
}],
"sum": {
"start": 2.001046,
"end": 3.001049,
"seconds": 1.0000029802322388,
"bytes": 3500015616,
"bits_per_second": 28000041481.373692,
"retransmits": 0,
"omitted": true,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 0.012641,
"end": 0.988366,
"seconds": 1.0010069608688354,
"bytes": 3449421824,
"bits_per_second": 27567615082.364941,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 58,
"rttvar": 7,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 0.012641,
"end": 0.988366,
"seconds": 1.0010069608688354,
"bytes": 3449421824,
"bits_per_second": 27567615082.364941,
"retransmits": 0,
"omitted": false,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 0.988366,
"end": 1.988398,
"seconds": 1.0000319480895996,
"bytes": 3414818816,
"bits_per_second": 27317677780.382618,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 61,
"rttvar": 9,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 0.988366,
"end": 1.988398,
"seconds": 1.0000319480895996,
"bytes": 3414818816,
"bits_per_second": 27317677780.382618,
"retransmits": 0,
"omitted": false,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 0.988366,
"end": 1.988398,
"seconds": 1.0000319480895996,
"bytes": 3414818816,
"bits_per_second": 27317677780.382618,
"retransmits": 0,
"snd_cwnd": 2029973,
"snd_wnd": 3145088,
"rtt": 61,
"rttvar": 9,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 0.988366,
"end": 1.988398,
"seconds": 1.0000319480895996,
"bytes": 3414818816,
"bits_per_second": 27317677780.382618,
"retransmits": 0,
"omitted": false,
"sender": true
}
}],
"end": {
},
"error": "control socket has closed unexpectedly"
}
Implementation Details
With this implementation, the server overrides the value of test->duration with the value that was passed in via the --server-time flag. We can see from the drop downs that the client's experience is similar to when the bitrate limit is exceeded with the error: iperf3: error - control socket has closed unexpectedly. However, the server side is less friendly, with an error of iperf3: error - select failed: Bad file descriptor. I can already envision myself encountering this error and not knowing why I am getting it, as well as forgetting that I had set the --server-time flag to begin with.
Another thing to note is that nothing is stopping the user from setting a high value for --omit. Given that this feature is meant to protect the server, is it reasonable to include a --server-omit option to enforce a maximum value?
Update (04/18/2024)
Since the first commit, I've introduced a solution for the error iperf3: error - select failed: Bad file descriptor which provides a more friendly experience. Let's consider the scenario where the --server-time flag has been set on the server to a value of 3:
- The client connects to the server
- The server accepts the connection, receives the client's cookie, and sends the client that value of
3for the server time - When the client's timers are created, it compares its duration to the server's duration of
3. If the client's duration is greater than the server's, then the server's value is selected for the timer. - The same process occurs when the server's timers are created.
The JSON output also has a new field which specifies the server's value for duration: server_duration
client_json
{
"start": {
"connected": [{
"socket": 5,
"local_host": "127.0.0.1",
"local_port": 36906,
"remote_host": "127.0.0.1",
"remote_port": 5201
}],
"version": "iperf 3.16+",
"system_info": "Linux windows-nexus 5.15.146.1-microsoft-standard-WSL2 #1 SMP Thu Jan 11 04:09:03 UTC 2024 x86_64",
"timestamp": {
"time": "Mon, 22 Apr 2024 02:23:53 GMT",
"timesecs": 1713752633
},
"connecting_to": {
"host": "127.0.0.1",
"port": 5201
},
"cookie": "m4n5p26auofjvyda2x2ba56kexzsly7mz5k7",
"tcp_mss_default": 32768,
"target_bitrate": 5000000,
"fq_rate": 0,
"sock_bufsize": 0,
"sndbuf_actual": 16384,
"rcvbuf_actual": 131072,
"test_start": {
"protocol": "TCP",
"num_streams": 1,
"blksize": 131072,
"omit": 0,
"duration": 6,
"server_duration": 3,
"bytes": 0,
"blocks": 0,
"reverse": 0,
"tos": 0,
"target_bitrate": 5000000,
"bidir": 0,
"fqrate": 0,
"interval": 1
}
},
"intervals": [{
"streams": [{
"socket": 5,
"start": 0,
"end": 1.000087,
"seconds": 1.0000870227813721,
"bytes": 655360,
"bits_per_second": 5242423.78970069,
"retransmits": 0,
"snd_cwnd": 654830,
"snd_wnd": 1375232,
"rtt": 38,
"rttvar": 24,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 0,
"end": 1.000087,
"seconds": 1.0000870227813721,
"bytes": 655360,
"bits_per_second": 5242423.78970069,
"retransmits": 0,
"omitted": false,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 1.000087,
"end": 2.000084,
"seconds": 0.99999701976776123,
"bytes": 655360,
"bits_per_second": 5242895.6250465661,
"retransmits": 0,
"snd_cwnd": 654830,
"snd_wnd": 2684928,
"rtt": 45,
"rttvar": 22,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 1.000087,
"end": 2.000084,
"seconds": 0.99999701976776123,
"bytes": 655360,
"bits_per_second": 5242895.6250465661,
"retransmits": 0,
"omitted": false,
"sender": true
}
}, {
"streams": [{
"socket": 5,
"start": 2.000084,
"end": 3.000148,
"seconds": 1.0000640153884888,
"bytes": 655360,
"bits_per_second": 5242544.39648379,
"retransmits": 0,
"snd_cwnd": 654830,
"snd_wnd": 3112448,
"rtt": 62,
"rttvar": 44,
"pmtu": 65535,
"omitted": false,
"sender": true
}],
"sum": {
"start": 2.000084,
"end": 3.000148,
"seconds": 1.0000640153884888,
"bytes": 655360,
"bits_per_second": 5242544.39648379,
"retransmits": 0,
"omitted": false,
"sender": true
}
}],
"end": {
"streams": [{
"sender": {
"socket": 5,
"start": 0,
"end": 3.000148,
"seconds": 3.000148,
"bytes": 1966080,
"bits_per_second": 5242621.36401271,
"retransmits": 0,
"max_snd_cwnd": 654830,
"max_snd_wnd": 3112448,
"max_rtt": 62,
"min_rtt": 38,
"mean_rtt": 48,
"sender": true
},
"receiver": {
"socket": 5,
"start": 0,
"end": 3.000258,
"seconds": 3.000148,
"bytes": 1966080,
"bits_per_second": 5242429.1510930061,
"sender": true
}
}],
"sum_sent": {
"start": 0,
"end": 3.000148,
"seconds": 3.000148,
"bytes": 1966080,
"bits_per_second": 5242621.36401271,
"retransmits": 0,
"sender": true
},
"sum_received": {
"start": 0,
"end": 3.000258,
"seconds": 3.000258,
"bytes": 1966080,
"bits_per_second": 5242429.1510930061,
"sender": true
},
"cpu_utilization_percent": {
"host_total": 100.87966904530634,
"host_user": 100.8797023673657,
"host_system": 0,
"remote_total": 0.037496475331318856,
"remote_user": 0.037496475331318856,
"remote_system": 0
},
"sender_tcp_congestion": "cubic",
"receiver_tcp_congestion": "cubic"
}
}
Update (06/05/2024)
A valid question was brought up by @TheRealDJ: What happens when the server has this feature but the client doesn't?. With the current implementation, there is no backwards compatability. Further discussions will need to be had in order to handle this case properly.