ipyrf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ipyrf-0.1.0/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Steinwurf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
ipyrf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: ipyrf
3
+ Version: 0.1.0
4
+ Summary: A tiny iperf3-like tool with JSON output (TCP/UDP)
5
+ Author: Steinwurf ApS
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Steinwurf
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+
29
+
30
+ Project-URL: Homepage, https://github.com/steinwurf/ipyrf
31
+ Project-URL: Repository, https://github.com/steinwurf/ipyrf
32
+ Project-URL: Issues, https://github.com/steinwurf/ipyrf/issues
33
+ Keywords: network,iperf,throughput,udp,tcp,benchmark
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3 :: Only
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: POSIX :: Linux
38
+ Classifier: Topic :: System :: Networking
39
+ Classifier: Topic :: System :: Benchmark
40
+ Requires-Python: >=3.8
41
+ Description-Content-Type: text/x-rst
42
+ License-File: LICENSE
43
+ Provides-Extra: test
44
+ Requires-Dist: pytest>=7.0; extra == "test"
45
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
46
+ Dynamic: license-file
47
+
48
+ ipyrf
49
+ =====
50
+
51
+ Minimal iperf3-like network throughput tool with JSON output. Supports TCP and UDP, server and client modes.
52
+
53
+ Features
54
+ --------
55
+ - TCP and UDP tests
56
+ - JSON or human-readable output
57
+ - Optional bandwidth capping (TCP/UDP)
58
+ - UDP packet loss estimation
59
+ - Linux TCP congestion control selection (if available)
60
+
61
+ Installation
62
+ ------------
63
+
64
+ From PyPI (recommended):
65
+
66
+ .. code-block:: bash
67
+
68
+ python3 -m pip install ipyrf
69
+
70
+ From source (editable):
71
+
72
+ .. code-block:: bash
73
+
74
+ python3 -m venv .venv
75
+ source .venv/bin/activate
76
+ python3 -m pip install -U pip build
77
+ python3 -m pip install -e .
78
+
79
+ Development
80
+ -----------
81
+
82
+ For development, install with test dependencies:
83
+
84
+ .. code-block:: bash
85
+
86
+ python3 -m pip install -e ".[test]"
87
+
88
+ Running Tests
89
+ ~~~~~~~~~~~~~
90
+
91
+ The project includes a comprehensive test suite using pytest:
92
+
93
+ .. code-block:: bash
94
+
95
+ # Run all tests
96
+ python -m pytest tests/
97
+
98
+ # Run only unit tests (fast)
99
+ python -m pytest tests/ -m "not integration"
100
+
101
+ # Run only integration tests
102
+ python -m pytest tests/ -m "integration"
103
+
104
+ # Run with coverage
105
+ python -m pytest tests/ --cov=src/ipyrf --cov-report=html
106
+
107
+ # Use the test runner script
108
+ ./run_tests.py --type fast --verbose
109
+
110
+ Test Categories
111
+ ~~~~~~~~~~~~~~~
112
+
113
+ - **Unit tests**: Fast tests that don't require network access
114
+ - **Integration tests**: Tests that require network functionality
115
+ - **Network tests**: Tests that create actual network connections
116
+ - **Slow tests**: Tests that take longer to run (marked with @pytest.mark.slow)
117
+
118
+ Continuous Integration
119
+ ~~~~~~~~~~~~~~~~~~~~~~
120
+
121
+ The project uses GitHub Actions for continuous integration, testing against Python 3.8-3.12.
122
+
123
+ Usage
124
+ -----
125
+
126
+ The package installs a console script named ``ipyrf``.
127
+
128
+ Quick examples
129
+ ~~~~~~~~~~~~~~
130
+
131
+ TCP server:
132
+
133
+ .. code-block:: bash
134
+
135
+ ipyrf tcp server 0.0.0.0 --port 12345
136
+
137
+ TCP client:
138
+
139
+ .. code-block:: bash
140
+
141
+ ipyrf tcp client 127.0.0.1 --port 12345 --time 5
142
+ ipyrf tcp client 127.0.0.1 --port 12345 --time 5 --set-mss 1400
143
+
144
+ UDP server:
145
+
146
+ .. code-block:: bash
147
+
148
+ ipyrf udp server 0.0.0.0 --port 12345
149
+
150
+ UDP client (with bandwidth cap and optional payload size):
151
+
152
+ .. code-block:: bash
153
+
154
+ ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5
155
+ ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5 -l 1200
156
+
157
+ Interactive mode
158
+ ----------------
159
+
160
+ You can run clients in an interactive mode that lets you adjust the pacing live using your keyboard. Use ``--interactive`` and optionally ``--interval`` (seconds between stats updates). When interactive is enabled, the same client logic is used underneath with a dynamic pacing controller.
161
+
162
+ Controls shown in the terminal:
163
+
164
+ - ``←``: -1 Mbps
165
+ - ``→``: +1 Mbps
166
+ - ``↓``: -10%
167
+ - ``↑``: +10%
168
+ - ``0``: reset to initial bandwidth (or unlimited for TCP if none was provided)
169
+ - ``u``: unlimited (disable pacing)
170
+ - ``q``: quit
171
+
172
+ Examples:
173
+
174
+ .. code-block:: bash
175
+
176
+ # TCP interactive (unlimited unless you pass --bandwidth)
177
+ ipyrf tcp client 127.0.0.1 --port 5201 --interactive
178
+
179
+ # TCP interactive with initial pacing and custom interval
180
+ ipyrf tcp client 127.0.0.1 --port 5201 --bandwidth 200M --set-mss 1400 --interactive --interval 0.5
181
+
182
+ # UDP interactive (requires initial --bandwidth)
183
+ ipyrf udp client 127.0.0.1 --port 5201 --bandwidth 50M -l 1200 --interactive
184
+
185
+ CLI overview
186
+ ------------
187
+
188
+ Top-level structure:
189
+
190
+ .. code-block:: text
191
+
192
+ ipyrf [tcp|udp] [server|client] [OPTIONS]
193
+
194
+ Common options (both protocols, both roles):
195
+
196
+ - ``--port``: Port (default 5201)
197
+ - ``--logfile``: Redirect output to a file
198
+ - ``--json_log``: Emit logs in JSON (newline-delimited)
199
+
200
+ TCP-specific options:
201
+
202
+ - ``tcp server ADDRESS``: Start a TCP server on ``ADDRESS``
203
+ - ``tcp client ADDRESS``: Start a TCP client to connect to ``ADDRESS``
204
+ - ``--congestion-control``: Select Linux TCP CC algorithm if available
205
+ - ``--time``: Test duration (seconds), default 10
206
+ - ``--bandwidth``: Target rate (e.g., ``50M``); used for pacing, optional
207
+ - ``--set-mss``: Set approximate MSS via ``TCP_MAXSEG``
208
+ - ``--interactive``: Enable interactive pacing controls
209
+ - ``--interval``: Stats interval in seconds for interactive mode (default 1.0)
210
+
211
+ UDP-specific options:
212
+
213
+ - ``udp server ADDRESS``: Start a UDP server on ``ADDRESS``
214
+ - ``udp client ADDRESS``: Start a UDP client to ``ADDRESS``
215
+ - ``--time``: Test duration (seconds), default 10
216
+ - ``--bandwidth``: Target rate (required for UDP client; e.g., ``50M``)
217
+ - ``-l/--length``: UDP payload length (default 1200)
218
+ - ``--interactive``: Enable interactive pacing controls
219
+ - ``--interval``: Stats interval in seconds for interactive mode (default 1.0)
220
+
221
+ JSON logging
222
+ ------------
223
+
224
+ Add ``--json_log`` to switch all output to newline-delimited JSON objects. This is useful for machine parsing or dashboards. Example:
225
+
226
+ .. code-block:: bash
227
+
228
+ ipyrf tcp client 127.0.0.1 --time 5 --json_log | jq
229
+
230
+ Notes
231
+ -----
232
+
233
+ - Output is JSON (newline-delimited for update events) when ``--json_log`` is given; otherwise, a human-readable summary is printed.
234
+ - UDP mode sends a FIN marker at the end and the server exits after FIN (or inactivity timeout).
235
+ - On Linux, congestion control selection is exposed if ``/proc`` entries are available.
236
+
237
+ License
238
+ -------
239
+
240
+ MIT. See ``LICENSE``.
ipyrf-0.1.0/README.rst ADDED
@@ -0,0 +1,193 @@
1
+ ipyrf
2
+ =====
3
+
4
+ Minimal iperf3-like network throughput tool with JSON output. Supports TCP and UDP, server and client modes.
5
+
6
+ Features
7
+ --------
8
+ - TCP and UDP tests
9
+ - JSON or human-readable output
10
+ - Optional bandwidth capping (TCP/UDP)
11
+ - UDP packet loss estimation
12
+ - Linux TCP congestion control selection (if available)
13
+
14
+ Installation
15
+ ------------
16
+
17
+ From PyPI (recommended):
18
+
19
+ .. code-block:: bash
20
+
21
+ python3 -m pip install ipyrf
22
+
23
+ From source (editable):
24
+
25
+ .. code-block:: bash
26
+
27
+ python3 -m venv .venv
28
+ source .venv/bin/activate
29
+ python3 -m pip install -U pip build
30
+ python3 -m pip install -e .
31
+
32
+ Development
33
+ -----------
34
+
35
+ For development, install with test dependencies:
36
+
37
+ .. code-block:: bash
38
+
39
+ python3 -m pip install -e ".[test]"
40
+
41
+ Running Tests
42
+ ~~~~~~~~~~~~~
43
+
44
+ The project includes a comprehensive test suite using pytest:
45
+
46
+ .. code-block:: bash
47
+
48
+ # Run all tests
49
+ python -m pytest tests/
50
+
51
+ # Run only unit tests (fast)
52
+ python -m pytest tests/ -m "not integration"
53
+
54
+ # Run only integration tests
55
+ python -m pytest tests/ -m "integration"
56
+
57
+ # Run with coverage
58
+ python -m pytest tests/ --cov=src/ipyrf --cov-report=html
59
+
60
+ # Use the test runner script
61
+ ./run_tests.py --type fast --verbose
62
+
63
+ Test Categories
64
+ ~~~~~~~~~~~~~~~
65
+
66
+ - **Unit tests**: Fast tests that don't require network access
67
+ - **Integration tests**: Tests that require network functionality
68
+ - **Network tests**: Tests that create actual network connections
69
+ - **Slow tests**: Tests that take longer to run (marked with @pytest.mark.slow)
70
+
71
+ Continuous Integration
72
+ ~~~~~~~~~~~~~~~~~~~~~~
73
+
74
+ The project uses GitHub Actions for continuous integration, testing against Python 3.8-3.12.
75
+
76
+ Usage
77
+ -----
78
+
79
+ The package installs a console script named ``ipyrf``.
80
+
81
+ Quick examples
82
+ ~~~~~~~~~~~~~~
83
+
84
+ TCP server:
85
+
86
+ .. code-block:: bash
87
+
88
+ ipyrf tcp server 0.0.0.0 --port 12345
89
+
90
+ TCP client:
91
+
92
+ .. code-block:: bash
93
+
94
+ ipyrf tcp client 127.0.0.1 --port 12345 --time 5
95
+ ipyrf tcp client 127.0.0.1 --port 12345 --time 5 --set-mss 1400
96
+
97
+ UDP server:
98
+
99
+ .. code-block:: bash
100
+
101
+ ipyrf udp server 0.0.0.0 --port 12345
102
+
103
+ UDP client (with bandwidth cap and optional payload size):
104
+
105
+ .. code-block:: bash
106
+
107
+ ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5
108
+ ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5 -l 1200
109
+
110
+ Interactive mode
111
+ ----------------
112
+
113
+ You can run clients in an interactive mode that lets you adjust the pacing live using your keyboard. Use ``--interactive`` and optionally ``--interval`` (seconds between stats updates). When interactive is enabled, the same client logic is used underneath with a dynamic pacing controller.
114
+
115
+ Controls shown in the terminal:
116
+
117
+ - ``←``: -1 Mbps
118
+ - ``→``: +1 Mbps
119
+ - ``↓``: -10%
120
+ - ``↑``: +10%
121
+ - ``0``: reset to initial bandwidth (or unlimited for TCP if none was provided)
122
+ - ``u``: unlimited (disable pacing)
123
+ - ``q``: quit
124
+
125
+ Examples:
126
+
127
+ .. code-block:: bash
128
+
129
+ # TCP interactive (unlimited unless you pass --bandwidth)
130
+ ipyrf tcp client 127.0.0.1 --port 5201 --interactive
131
+
132
+ # TCP interactive with initial pacing and custom interval
133
+ ipyrf tcp client 127.0.0.1 --port 5201 --bandwidth 200M --set-mss 1400 --interactive --interval 0.5
134
+
135
+ # UDP interactive (requires initial --bandwidth)
136
+ ipyrf udp client 127.0.0.1 --port 5201 --bandwidth 50M -l 1200 --interactive
137
+
138
+ CLI overview
139
+ ------------
140
+
141
+ Top-level structure:
142
+
143
+ .. code-block:: text
144
+
145
+ ipyrf [tcp|udp] [server|client] [OPTIONS]
146
+
147
+ Common options (both protocols, both roles):
148
+
149
+ - ``--port``: Port (default 5201)
150
+ - ``--logfile``: Redirect output to a file
151
+ - ``--json_log``: Emit logs in JSON (newline-delimited)
152
+
153
+ TCP-specific options:
154
+
155
+ - ``tcp server ADDRESS``: Start a TCP server on ``ADDRESS``
156
+ - ``tcp client ADDRESS``: Start a TCP client to connect to ``ADDRESS``
157
+ - ``--congestion-control``: Select Linux TCP CC algorithm if available
158
+ - ``--time``: Test duration (seconds), default 10
159
+ - ``--bandwidth``: Target rate (e.g., ``50M``); used for pacing, optional
160
+ - ``--set-mss``: Set approximate MSS via ``TCP_MAXSEG``
161
+ - ``--interactive``: Enable interactive pacing controls
162
+ - ``--interval``: Stats interval in seconds for interactive mode (default 1.0)
163
+
164
+ UDP-specific options:
165
+
166
+ - ``udp server ADDRESS``: Start a UDP server on ``ADDRESS``
167
+ - ``udp client ADDRESS``: Start a UDP client to ``ADDRESS``
168
+ - ``--time``: Test duration (seconds), default 10
169
+ - ``--bandwidth``: Target rate (required for UDP client; e.g., ``50M``)
170
+ - ``-l/--length``: UDP payload length (default 1200)
171
+ - ``--interactive``: Enable interactive pacing controls
172
+ - ``--interval``: Stats interval in seconds for interactive mode (default 1.0)
173
+
174
+ JSON logging
175
+ ------------
176
+
177
+ Add ``--json_log`` to switch all output to newline-delimited JSON objects. This is useful for machine parsing or dashboards. Example:
178
+
179
+ .. code-block:: bash
180
+
181
+ ipyrf tcp client 127.0.0.1 --time 5 --json_log | jq
182
+
183
+ Notes
184
+ -----
185
+
186
+ - Output is JSON (newline-delimited for update events) when ``--json_log`` is given; otherwise, a human-readable summary is printed.
187
+ - UDP mode sends a FIN marker at the end and the server exits after FIN (or inactivity timeout).
188
+ - On Linux, congestion control selection is exposed if ``/proc`` entries are available.
189
+
190
+ License
191
+ -------
192
+
193
+ MIT. See ``LICENSE``.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ipyrf"
7
+ version = "0.1.0"
8
+ description = "A tiny iperf3-like tool with JSON output (TCP/UDP)"
9
+ readme = "README.rst"
10
+ requires-python = ">=3.8"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "Steinwurf ApS" }
14
+ ]
15
+ keywords = ["network", "iperf", "throughput", "udp", "tcp", "benchmark"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Topic :: System :: Networking",
22
+ "Topic :: System :: Benchmark",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.optional-dependencies]
27
+ test = ["pytest>=7.0", "pytest-cov>=4.0"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/steinwurf/ipyrf"
31
+ Repository = "https://github.com/steinwurf/ipyrf"
32
+ Issues = "https://github.com/steinwurf/ipyrf/issues"
33
+
34
+ [project.scripts]
35
+ ipyrf = "ipyrf.__main__:main"
36
+
37
+ [tool.setuptools]
38
+ package-dir = {"" = "src"}
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+ include = ["ipyrf*"]
43
+ exclude = []
ipyrf-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """ipyrf package: a tiny iperf3-like tool with JSON output."""
2
+
3
+ __all__ = [
4
+ "main",
5
+ ]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+ import argparse
5
+ import sys
6
+
7
+ from .logger import Logger
8
+ from .utils import parse_bandwidth, parse_ip, tcp_congestion_control_info
9
+ from . import tcp, udp
10
+ from .interactive import InteractiveController
11
+ from .controllers import StaticPacingController
12
+
13
+
14
+ def main():
15
+ p = argparse.ArgumentParser(description="Minimal iperf3-like tool (JSON output)")
16
+
17
+ common = argparse.ArgumentParser(add_help=False)
18
+ common.add_argument("--port", type=int, default=5201, help="Port number")
19
+ common.add_argument(
20
+ "--logfile", help="Write log messages to a file instead of stdout"
21
+ )
22
+ common.add_argument(
23
+ "--json_log",
24
+ help="Write the log messages as JSON.",
25
+ action="store_true",
26
+ default=False,
27
+ )
28
+ common.add_argument("--interval", type=float, default=1.0, help="Stats interval")
29
+
30
+ subp = p.add_subparsers(dest="protocol", required=True)
31
+
32
+ tcp_parser = subp.add_parser("tcp", help="TCP mode")
33
+ tcp_sub = tcp_parser.add_subparsers(dest="role", required=True)
34
+
35
+ congestion_control = tcp_congestion_control_info()
36
+ common_tcp = argparse.ArgumentParser(add_help=False)
37
+ common_tcp.add_argument(
38
+ "--congestion-control",
39
+ choices=congestion_control.get("allowed", []),
40
+ default=None,
41
+ help=(
42
+ argparse.SUPPRESS
43
+ if congestion_control == {}
44
+ else (
45
+ "TCP: set congestion control algorithm "
46
+ f"(default: system default '{congestion_control.get('current')}')"
47
+ )
48
+ ),
49
+ )
50
+
51
+ tcp_srv = tcp_sub.add_parser(
52
+ "server", parents=[common, common_tcp], help="Run a TCP server"
53
+ )
54
+ tcp_srv.add_argument(
55
+ "address", metavar="ADDRESS", type=parse_ip, help="Listen address"
56
+ )
57
+
58
+ tcp_cli = tcp_sub.add_parser(
59
+ "client", parents=[common, common_tcp], help="Run a TCP client"
60
+ )
61
+ tcp_cli.add_argument(
62
+ "--bandwidth", type=parse_bandwidth, help="Target bandwidth, e.g., 50M"
63
+ )
64
+ tcp_cli.add_argument("address", metavar="ADDRESS", help="Server address to connect")
65
+ tcp_cli.add_argument(
66
+ "--set-mss", dest="set_mss", type=int, help="TCP: set TCP_MAXSEG (approx MSS)"
67
+ )
68
+
69
+ # Time and interactive mode are mutually exclusive
70
+ tcp_time_group = tcp_cli.add_mutually_exclusive_group()
71
+ tcp_time_group.add_argument(
72
+ "--time", type=int, default=10, help="Test duration in seconds"
73
+ )
74
+ tcp_time_group.add_argument(
75
+ "--interactive", action="store_true", help="Run client in interactive mode"
76
+ )
77
+
78
+ udp_parser = subp.add_parser("udp", help="UDP mode")
79
+ udp_sub = udp_parser.add_subparsers(dest="role", required=True)
80
+
81
+ udp_srv = udp_sub.add_parser("server", parents=[common], help="Run a UDP server")
82
+ udp_srv.add_argument(
83
+ "address", metavar="ADDRESS", type=parse_ip, help="Listen address"
84
+ )
85
+
86
+ udp_cli = udp_sub.add_parser("client", parents=[common], help="Run a UDP client")
87
+ udp_cli.add_argument("address", metavar="ADDRESS", help="Server address to connect")
88
+ udp_cli.add_argument(
89
+ "--bandwidth", type=parse_bandwidth, help="Target bandwidth, e.g., 50M"
90
+ )
91
+ udp_cli.add_argument(
92
+ "-l", dest="length", type=int, default=1200, help="UDP payload length"
93
+ )
94
+
95
+ # Time and interactive mode are mutually exclusive
96
+ udp_time_group = udp_cli.add_mutually_exclusive_group()
97
+ udp_time_group.add_argument(
98
+ "--time", type=int, default=10, help="Test duration in seconds"
99
+ )
100
+ udp_time_group.add_argument(
101
+ "--interactive", action="store_true", help="Run client in interactive mode"
102
+ )
103
+
104
+ args = p.parse_args()
105
+
106
+ if args.role not in ("server", "client"):
107
+ raise ValueError(f"Invalid role: {args.role}. Must be 'server' or 'client'.")
108
+
109
+ log = Logger(args.json_log, args.protocol, args.role, args.logfile)
110
+
111
+ controller = None
112
+ if args.protocol == "udp":
113
+ if args.role == "server":
114
+ udp.server(log, args.address, args.port, args.interval)
115
+ else:
116
+ bw = (
117
+ args.bandwidth or parse_bandwidth("50M")
118
+ if args.interactive
119
+ else (args.bandwidth or 1e9)
120
+ )
121
+ if args.interactive:
122
+ controller = InteractiveController(bw, args.length, args.interval)
123
+ else:
124
+ controller = StaticPacingController(
125
+ bw, max(args.length, udp.UDP_HDR.size), args.time, args.interval
126
+ )
127
+ udp.client(
128
+ log,
129
+ args.address,
130
+ args.port,
131
+ args.length,
132
+ controller,
133
+ )
134
+
135
+ else:
136
+ if args.role == "server":
137
+ tcp.server(
138
+ log, args.address, args.port, args.interval, args.congestion_control
139
+ )
140
+ else:
141
+ if args.interactive:
142
+ quantum = args.set_mss if args.set_mss else 1200
143
+ # If no bandwidth provided, controller will act as unlimited until adjusted
144
+ controller = InteractiveController(
145
+ args.bandwidth, quantum, args.interval
146
+ )
147
+ else:
148
+ controller = StaticPacingController(
149
+ args.bandwidth,
150
+ args.set_mss if args.set_mss else 1200,
151
+ args.time,
152
+ args.interval,
153
+ )
154
+ tcp.client(
155
+ log,
156
+ args.address,
157
+ args.port,
158
+ args.congestion_control,
159
+ args.set_mss,
160
+ controller,
161
+ )
162
+
163
+ if controller is not None:
164
+ controller.stop()