Compare commits

...

48 Commits

Author SHA1 Message Date
irene ff423f8bbf ci: split into fast analyze + slow release build with path gating
analyze runs on every push/PR (fast check).
release-apk only fires on push to master or workflow_dispatch (slow build).
Doc-only pushes no longer trigger any CI run.
workflow_dispatch triggers both jobs unconditionally.
2026-08-27 23:59:04 +09:00
irene 47d849a122 ci: split into fast analyze + slow release build with path gating
analyze runs on every push/PR (fast check).
release-apk only fires on push to master or workflow_dispatch (slow build).
Doc-only pushes no longer trigger any CI run.
workflow_dispatch triggers both jobs unconditionally.
2026-08-27 23:58:02 +09:00
irene ba7f335b67 ci: split into fast analyze + slow release build with path gating
analyze job runs on push/PR regardless of changed files (fast check).
release-apk job only fires when Dart source, Android, pubspec, or
tooling files change on push to master (slow build).
workflow_dispatch triggers both jobs unconditionally.
Doc-only pushes no longer trigger any CI run.
2026-08-27 23:56:31 +09:00
irene 5b6c2c0435 fix(docs): correct iperf3 version from 3.21 to 3.19 in LICENSE
CI / Analyze & Test (push) Successful in 14s
CI / Release APK (push) Successful in 22s
2026-08-27 00:06:04 +09:00
irene b01f8cdf87 docs: add MIT LICENSE file for Gitea detection
CI / Analyze & Test (push) Successful in 10s
CI / Release APK (push) Successful in 23s
2026-08-27 00:02:44 +09:00
mikkeli b960581fd1 ci: allow manual runs with workflow_dispatch
CI / Analyze & Test (push) Successful in 9s
CI / Release APK (push) Successful in 23s
The workflow only triggered on push and pull_request, so a run that failed for
reasons outside the repo — runner down, toolchain changed underneath it —
could only be retried with an empty commit. That is a likelier situation with a
self-hosted runner than with hosted ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:57:33 +09:00
mikkeli 7981ca2022 fix(ci): register from the runner's own directory with an absolute state path
CI / Analyze & Test (push) Successful in 11s
CI / Release APK (push) Successful in 22s
Registration failed with `open .runner: permission denied`. runner.file was
the relative `.runner`, which resolves against the caller's working directory
— the repo the admin ran sudo from, which the service account cannot write.
The same applied to .runner.lock, so the single-process guard was skipped too.

Two fixes rather than one, because either alone leaves a trap: runner.file is
now absolute, and register runs with cwd set to the runner's home. An earlier
draft did cd there; that was lost when the script was rewritten for a system
service, and an absolute path is what makes it not matter next time.

Also drops --labels from the register call, which gitea-runner ignores and
warns about whenever the config file defines labels, and rewrites a config that
exists while nothing is registered yet — such a config is provisional, not in
use, and leaving a broken one in place was what required manual cleanup here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:53:04 +09:00
mikkeli ae2033865c fix(ci): probe writability by writing, not with test -w
CI / Analyze & Test (push) Successful in 12s
CI / Release APK (push) Successful in 22s
The setup script refused to provision, reporting /opt/flutter/bin/cache and
/opt/android-sdk unwritable by the service account, while a touch as that same
account succeeded.

Cause: this machine's /usr/bin/test is uutils coreutils 0.8.0, whose -w does
not credit supplementary group membership. Write access to both directories
comes from the flutterdev group rather than the owner bits, so -w reports false
for any account that should be building here — including the developer's own.
-r and -x pass only because `other` carries r-x, which is what made the failure
look ACL-shaped. bash's builtin [ -w ] evaluates it correctly, but sudo execs
the external binary, so the builtin was never in play.

Replaced with a real write probe: create a file as the service account and
remove it. That tests the operation a build actually performs and is immune to
whichever test implementation is installed, which is the better check
regardless of the bug that prompted it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:50:09 +09:00
mikkeli 86b290d978 ci: run the runner as a dedicated account under a system service
CI / Analyze & Test (push) Successful in 13s
CI / Release APK (push) Successful in 24s
The earlier script installed a systemd *user* service, which conflated two
separate decisions. What matters in host mode is which account jobs run as,
because there is no container boundary and the service account is the only
sandbox. A system service with User= set gets unprivileged jobs *and* a
root-managed lifecycle that starts at boot with no lingering session.

Jobs now run as a dedicated `gitea-runner` account in the flutterdev group,
with its own HOME so its Gradle and pub caches are its own. The alternative —
a human account — would hand every workflow that user's home directory and SSH
keys, which is a sharper edge than usual here because a workflow file is code
and much of this repo is agent-authored. Running as root would be worse still,
and would also miss the warm caches in a human HOME and re-download
dependencies every run.

flutterdev is what grants write access to /opt/flutter and /opt/android-sdk.
Group membership alone does not prove it, since both carry ACLs, so the script
tests writability as the service account and fails with the getfacl command to
run. Tool lookups use systemd's default PATH rather than an interactive shell's,
because that is what the service will actually have.

Adds --check, which runs pre-flight and exits without creating anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:44:05 +09:00
mikkeli 32575530dc Merge branch 'ci/self-hosted-x86-64-runner'
CI / Analyze & Test (push) Successful in 19s
CI / Release APK (push) Successful in 1m53s
Move CI to a self-hosted x86_64 runner registered at android-stuff org scope.
Flutter cannot build an Android APK on a Linux arm64 host, so the aarch64 Gitea
box could never have run this build regardless of its container networking.

Adds tool/setup_ci_runner.sh to provision it in host mode, using the machine's
existing proven toolchain, and points both jobs at the capability label
flutter-android.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:21:28 +09:00
mikkeli 0dd0b95afb ci: run on the self-hosted x86_64 runner and drop the toolchain installers
Targets runs-on: flutter-android for both jobs. Since that runner is host mode
with the toolchain preinstalled, subosito/flutter-action and
actions/setup-java are removed — they would have downloaded a second Flutter
and JDK over the ones being used, and CI would no longer be building with the
toolchain the release was verified against. A Toolchain step fails loudly if
flutter or ANDROID_HOME is missing rather than letting a later step fail
obscurely.

Documents in docs/README.md why the runner cannot be the Gitea host, what host
mode trades away, and what each job protects — including why building the
release APK is necessary but not sufficient, which is the reason the JNI name
guard exists at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:21:17 +09:00
mikkeli 66826bbce6 ci: add a provisioning script for an x86_64 Android build runner
Gitea runs on an aarch64 host, and Flutter cannot build an Android APK on a
Linux arm64 host at all: the SDK ships no arm64-host gen_snapshot
(flutter/flutter#189724, #75864, open as of July 2026). So CI for this repo was
failing for two independent reasons, and fixing the runner's container network
would only have exposed the second. An x86_64 runner is a requirement here, not
a preference.

Registers at android-stuff org scope rather than repo scope, so any Android
repo in the org can use the same runner, and labels it by capability
(flutter-android) rather than by hostname, so the workflow does not encode
which machine happens to provide it.

Host mode rather than a container: the machine already has a proven Flutter,
JDK and Android SDK/NDK, so jobs build with the same toolchain that produced
the verified release and skip a per-run NDK download. The trade is isolation,
which the header states plainly — workflows run as that user on that machine.

Refuses to register unless the toolchain it would advertise is actually
present, so a missing SDK fails at setup with a list rather than inside every
later job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:21:17 +09:00
mikkeli 85dc74247c Merge branch 'ci/release-apk-and-jni-name-guard'
CI / Analyze & Test (push) Failing after 28s
CI / Release APK (push) Failing after 1m48s
Add a CI job that builds the release APK and verifies the names JNI resolves
survived R8, so the broken-release-APK failure mode cannot recur silently.

CI's existing failures are unrelated to workflow content: every run has failed
at actions/checkout because the runner's job containers cannot resolve the
'gitea' hostname. That needs container.network set in the runner's config.yaml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:54:33 +09:00
mikkeli 94d96297f8 ci: build the release APK and check the JNI names
CI only ran analyze and test, so it would have passed for every commit that
carried the broken release APK. Adds a release-apk job that builds the
minified artifact, runs tool/check_jni_keep_rules.sh against it, and uploads
it — turning "the shipping artifact works" into something checked rather than
assumed at tag time.

Enables flutter-action's dependency cache on both jobs, and notes that the
test step now includes the layering guard.

This does not fix the current failures: every run so far has failed at
actions/checkout, before Flutter is reached, because the runner's job
containers cannot resolve the `gitea` hostname. That is runner configuration,
not workflow content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:54:19 +09:00
mikkeli a96f302dc7 test: guard the JNI-visible names against R8 renaming
A renamed JNI callback does not fail the build — it produces a release APK
that aborts on the first callback of every test. That shipped once, because
every on-device check used the debug build, which does not minify.

This reads R8's own mapping file for the classes and callback methods, and the
DEX string pool for the native entry points. Native methods are deliberately
not checked against mapping.txt: R8 only emits entries for members with a body
or a rename, so a native method is absent either way and absence would prove
nothing.

Verified by deleting the callback keep rules and rebuilding: the check reports
MainThreadCallback renamed along with onOutput, onError and onComplete, which
is exactly the failure that shipped — with no device involved.

Also avoids `... | strings | grep -q` and dumps to a file first. Under
`set -o pipefail`, grep -q exits at the first match and SIGPIPEs strings, so
the pipeline reports failure for a name found early and success for one found
late. It reported two of four names missing while all four were present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:54:19 +09:00
mikkeli 7ca74366a3 Merge branch 'fix/release-build-jni-keep-rules'
CI / Analyze & Test (push) Failing after 30s
Release builds aborted on the first callback of every test: R8 renamed the
IperfCallback implementation and its methods, so the JNI GetMethodID lookups
resolved nothing. Adds the keep rules, and makes the native side report a
failed lookup instead of aborting the process on the next JNI call.

Debug builds do not minify, so every on-device check until now passed while the
shipping artifact was broken. Found by running the release APK during release
preparation.

Verified with the release APK on an XQ-AS72: clean run at 806 Mbits/sec,
matching the server's own figure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:43:52 +09:00
mikkeli ff13d63333 fix(jni): fail cleanly when a callback method does not resolve
GetMethodID raises NoSuchMethodError when a lookup fails, and any further JNI
call made while an exception is pending aborts the process. runIperfLive
resolved three method IDs back to back without checking, so the first failure
made the second call fatal — turning a diagnosable configuration problem into
a SIGABRT with a stack that pointed at the JNI entry rather than the cause.

resolve_method() clears the pending exception and returns NULL, and the caller
now bails out with a log naming which lookups failed and pointing at the keep
rules. The R8 problem this exposed is fixed separately; this is about the
failure being reportable rather than fatal, whatever the cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:43:35 +09:00
mikkeli 2b3f20d3a0 fix(android): keep the JNI callback names through R8
Release builds aborted on the first callback of every test:

  NoSuchMethodError: no non-static method "Lp0;.onOutput(Ljava/lang/String;)V"

R8 had renamed MainThreadCallback to p0 along with its methods, so
GetMethodID(env, cls, "onOutput", ...) in iperf_jni.c resolved nothing. The
names are a contract between C and Kotlin that the minifier cannot see.

Adds keep rules for IperfRunner, the IperfCallback interface and — the part
that matters — everything implementing it, since the lookup happens on the
concrete object's class rather than on the interface. A general rule keeps any
class declaring a native method, so a future one is covered without a new rule.

Debug builds do not minify, which is why every on-device check so far passed
while the shipping artifact was broken. Found by running the release APK
during release preparation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:43:35 +09:00
mikkeli 894712b7a8 chore: set version to 0.1.0 for the first release
pubspec.yaml still carried flutter create's placeholder 1.0.0+1, which would
have had the APK reporting 1.0.0 while the release was tagged v0.1.0. Matches
the version the tag records.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:38:48 +09:00
mikkeli 895b963745 fix: restore speedtest.uztelecom.uz as the default server
Consolidating the UI's seed values onto TestConfiguration's own defaults also
silently changed the default target to iperf.es.net, which was not asked for.
Sets it back in TestConfiguration, which is now the single definition the
screen and the headless entry point both read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:38:19 +09:00
mikkeli b76098c7e6 Merge branch 'refactor/core-independent-of-ui'
CI / Analyze & Test (push) Failing after 30s
Make the core work independently of the UI: Iperf3Session accumulates a run in
the domain layer, so the view model holds no run data and the screen derives no
numbers. Replacing the presentation layer now means rewriting rendering, not
re-deriving results.

Adds lib/main_headless.dart, which runs a full test through Iperf3Service and
Iperf3Session with no widget tree, as a standing check on that claim — and as
the accurate way to measure: 30.2% local CPU headless against 79.9% through the
UI on a comparable 4-stream run.

Verified on an XQ-AS72: headless run clean at 859 Mbit/s mean, matching the
server's own 859 Mbits/sec receiver figure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:25:24 +09:00
mikkeli 6e35d724dc docs: record that the core runs without the UI, and the CPU cost of the UI
States where run data lives now that Iperf3Session owns it, so the next person
does not put accumulation back in the view model, and documents the headless
entry point as the way to check that claim rather than trust it.

Includes the measurement that motivates using it: 30.2% local CPU headless
versus 79.9% through the UI on a comparable 4-stream run. That gap is worth
recording, because it means throughput read off the app's own screen is
pessimistic at high rates and should not be compared against a headless or
desktop figure without noting which path produced it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:25:06 +09:00
mikkeli fc15897541 feat: add a headless entry point that runs a test with no UI
Separation that nothing exercises is separation that quietly stops being true,
so this runs a full test through Iperf3Service and Iperf3Session with
WidgetsFlutterBinding but no runApp: no widget tree, no render surface. If the
run path ever starts needing the UI, this stops working. An architecture test
additionally forbids it importing lib/ui.

It is also the accurate way to measure. A 4-stream run against a local server
reports 30.2% local CPU here, against 79.9% for a comparable run with the
transcript repainting alongside — the UI was costing roughly fifty points of
CPU on a phone already near CPU-bound at gigabit rates. Throughput figures
taken through the UI are therefore pessimistic, and this is the path to use
when the number matters.

Parameters come from --dart-define so a run needs no code edit:
  flutter run -t lib/main_headless.dart --dart-define=IPERF_SERVER=…

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:24:44 +09:00
mikkeli 9485049b57 refactor: move run accumulation out of the view model into the domain
The view model was doing two jobs. Alongside presentation state it held what
happened during a run — the interval series, the rule that iperf3's omitted
warm-up intervals do not count, the retained end summary — and the screen
computed mean and peak inline in a widget builder. All of that is the part
that has to be correct, and all of it would have been rewritten by anyone
replacing the UI.

Iperf3Session accumulates a run in the domain layer: samples, totals, errors,
whether it was stopped, and the statistics over them. The view model now feeds
it events and keeps only a formatted transcript and the repaint schedule; the
screen reads every displayed number off the session and derives none.

Two details the session now owns rather than leaving to a caller. Omitted
intervals are excluded, so a warm-up cannot drag the mean down. And
totalRetransmits stays null when no interval reported a count — UDP does not
measure retransmits, and "not measured" must not render as zero.

The aggregation tests move with the logic, into a pure-Dart test with no
widgets or bindings involved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:24:44 +09:00
mikkeli 838c883cc0 Merge branch 'refactor/separate-ui-from-data'
CI / Analyze & Test (push) Failing after 38s
Separate presentation from the data layer: the UI depends on an Iperf3Runner
interface declared in the domain layer, and main.dart is the only file that
names a concrete implementation.

Adds widget tests that drive the real UI against an in-memory runner with no
MethodChannel or native library, and an architecture test that reads the
imports under lib/ and fails the build when a layer boundary is crossed.

Verified on an XQ-AS72 after the change: clean run, iperf_run_client returned
0, server logged 608 Mbits/sec, no ANR or native abort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:41 +09:00
mikkeli fe042efc29 docs: record the layering rule and where it is enforced
The architecture section listed files but said nothing about which layer may
import what, so the direction of dependency was only visible by reading the
imports. States the rule as a table, notes that the UI depends on the
Iperf3Runner contract rather than on a transport and what that buys, and points
at the test that enforces it so the rule is not just prose.

Also brings the tree up to date: it was missing the domain services directory,
the event model and the formatter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:25 +09:00
mikkeli 0cf019379a test: fail the build when a layer boundary is crossed
The layering only holds if something checks it, so these tests read the real
import statements under lib/ and assert the direction: domain imports no
Flutter and nothing else in the package, data never reaches into ui and may
import flutter/services alone, and ui never names a concrete transport.

A final test asserts the set of directories under lib/ is exactly the three
layers, so a new one cannot appear and silently escape every rule above.

Each violation reports the offending file and import with the reason, since a
guard that only says "expected empty" costs more than it saves. Verified by
introducing a UI-to-data import and a Flutter import in domain and confirming
both fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:03 +09:00
mikkeli 95acf5ea38 test: drive the real UI against an in-memory runner
FakeIperf3Runner implements Iperf3Runner, so the widget tests exercise the
actual widget tree — GO, STOP, the status line, the stat row, validation —
with no MethodChannel and no native library. That was impossible while the
screen constructed its own Iperf3Service.

The view model tests share the same fake instead of each file carrying a
double that had to mirror the concrete service.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:03 +09:00
mikkeli 314a3ea059 refactor: depend on an Iperf3Runner interface instead of the service
The presentation layer imported Iperf3Service directly, so the UI was welded
to the MethodChannel transport: the view model named it, and the screen
constructed one. Dependency direction was already inward-only, but the UI
depended on an implementation rather than a contract.

Iperf3Runner states that contract in the domain layer in five members, in pure
Dart with no Flutter import, and Iperf3Service implements it. The view model
and screen now take an Iperf3Runner; main.dart is the only file that names a
concrete one, which makes it the composition root.

Two consequences worth having. The UI can be driven against an in-memory
runner with no MethodChannel, native library or device involved. And a test
double implements five stable members rather than tracking every public member
the transport happens to expose — the previous double, forced to implement the
concrete class, broke twice while this branch's work was going on.

Also drops flutter/foundation from the data layer: debugPrint gives way to
dart:developer, leaving flutter/services (where MethodChannel lives) as that
layer's only Flutter dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:03 +09:00
mikkeli defd64a073 Merge branch 'refactor/json-stream-events'
CI / Analyze & Test (push) Failing after 34s
Replace the stdout pipe with iperf3's json_callback hook and consume the
resulting --json-stream events as structured data: interval samples with
retransmits and per-stream detail, and an end summary with sent/received
totals and CPU utilisation.

Deletes the pipe, the reader thread and the join-ordering and ownership
constraints that came with them.

Verified on an XQ-AS72 against a local iperf3 server: 4 streams at 889
Mbits/sec, matching the server's own 887 Mbits/sec receiver figure. Across
four consecutive runs thread count returned to baseline and fd count returned
exactly to baseline, with no ANR or native abort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:55 +09:00
mikkeli 857b79fbaf docs: replace the pipe design with json_callback and note the embedding costs
The output-streaming section described the pipe and reader thread that no
longer exist, including the join-ordering rule that was the reason they were
dangerous. Replaced with how the json_callback path works and why, and an
explicit note not to reintroduce the pipe.

Adds a section on what embedding a CLI in a long-lived app process actually
costs, since that is the recurring trap here: iperf3's main() can leave
process-global state behind because it is about to exit, and this app cannot.
Records what iperf3 does clean up — joined stream threads, closed fds, an
immediately unlinked temp file — with the measurements taken on device, so the
next person does not have to re-derive whether repeated runs leak.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:41 +09:00
mikkeli cbb2aba8b0 feat(ui): render the event stream and show live throughput figures
Consuming JSON means the familiar iperf3 text layout has to be rebuilt, so a
pure formatter turns each event back into a line — binary units for transfer
totals, decimal units for bitrates, matching how iperf3 quotes each. The view
model keeps the interval series and the end summary as data alongside the
transcript, and a stat row shows current/total, mean, peak and sample count
off that series. A chart can be driven from the same list without re-parsing
text.

Repaints are coalesced on a 250ms timer rather than one per event. Reports can
now arrive several times a second across many streams, and each rebuild
re-renders the whole transcript, so notifying per event made the cost of
drawing scale with the report rate — which competes with the test itself for
CPU and skews the measurement.

Warm-up intervals (-O) are marked omitted by iperf3 and are kept out of the
series so they cannot drag the mean down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:10 +09:00
mikkeli 7d8d06e2e5 feat(app): parse json-stream reports into structured events
The native layer now emits one JSON object per report, so the service parses
each into a sealed Iperf3Event — start, interval, end, error — and publishes
those instead of opaque lines. Interval samples carry bytes, bits per second,
retransmits and per-stream socket/cwnd/rtt detail; the end event carries sent
and received totals and CPU utilisation. None of that was available while the
output was scraped text.

Anything unrecognised — non-JSON, malformed JSON, an event name a future
iperf3 adds — becomes an Iperf3LogLine rather than being dropped, so
unexpected output still reaches the user.

Failures arrive as an `error` event rather than a non-zero return from
iperf_run_client, so that event is what now marks a run as failed. The
result's rawOutput keeps the JSON lines verbatim, since that is what the
native layer actually produced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:10 +09:00
mikkeli 598a2d99c9 refactor(jni): take results from json_callback instead of piping stdout
iperf3 exposes a hook for exactly this use — iperf.h describes json_callback
as "allow user apps to receive the JSON strings, instead of writing them to
the output file". Setting it alongside --json-stream's two flags makes iperf3
hand each report ("start", "interval", "end", "error") straight to the bridge
as a JSON string, and test->outfile is never touched.

That removes the pipe, the reader thread, the fdopen/fgets loop, and
AttachCurrentThread from the output path — roughly 90 lines. It also removes
the two constraints that came with them and were both gotten wrong: that the
write end must be closed before joining a reader blocked in fgets(), and that
exactly one side may own the shared state struct. Neither can be violated now
because neither exists.

json_callback carries no user-data pointer, so the run state is reached
through a global published before iperf_run_client() and cleared after it.
Only one test runs at a time, which runIperfLive already enforced. The
callback fires on iperf3's reporting thread, so it resolves a JNIEnv for the
calling thread rather than trusting a stored one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:10 +09:00
mikkeli d448d5f885 Merge branch 'fix/iperf3-test-execution'
CI / Analyze & Test (push) Failing after 34s
Make iperf3 tests actually run on device: fix the end-of-test deadlock and
memory ownership in the JNI bridge, move the blocking test run off the
Flutter platform thread, stream output to the UI as it arrives, and report a
user-requested stop as a stop rather than a failure.

Verified end to end on an XQ-AS72 against a local iperf3 server: 790 Mbits/sec
over a full 10s run, mid-run stop, and back-to-back runs, with no ANR or
native abort in logcat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:08:38 +09:00
mikkeli 0dc747edef docs: describe the JNI architecture the app actually uses
The root README still documented the abandoned approach: iperf3 3.21 binaries
bundled in assets and run through a Process runner, and a lib/ layout that no
longer matched. It now covers the JNI design, the current feature set and how
to test against a local iperf3 server, and says plainly that the binaries
still sitting in assets/iperf3/ are undeclared in pubspec.yaml and therefore
never reach the APK.

docs/README.md gains the threading, pipe-shutdown and stop-handling
constraints that the fixes on this branch depend on, so the next change does
not have to rediscover why the join order and the worker thread matter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:59 +09:00
mikkeli 6f809913bd chore: drop leftovers from the pre-JNI binary-execution approach
iperf3_binary_path_provider.dart held no code, only a comment saying the
extract-and-exec approach it belonged to had been replaced. path_provider was
a dependency of that same approach and no longer has a single usage; removing
it regenerates the desktop plugin registrants.

Also drops an unused import in main.dart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:59 +09:00
mikkeli 02a8ba9c18 fix: report a user-requested stop as stopped rather than failed
Stopping a test shuts down the control socket, so iperf_run_client() returns
-1 with "unable to send control message ... Broken pipe". That is the
expected consequence of the stop, not a fault, but the bridge read the
negative return as a failure — so pressing STOP left the UI showing
"Error (exit 1)" above a broken-pipe message.

The bridge now consults its own g_stop flag before classifying the outcome,
and the distinction travels up as Iperf3Result.stopped so the status line can
say "Stopped". A stop is reported with exit code 0: the run ended the way the
user asked it to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:59 +09:00
mikkeli fb6ec3af37 feat(ui): show live output, add a stop button and duration/stream inputs
The screen could only start a 10s single-stream test and read the result
afterwards. It now renders the transcript as it arrives in a scrolling
monospace view that follows the newest line, exposes the duration and
parallel-stream settings TestConfiguration already carried, can stop a run in
progress, and copies the transcript to the clipboard.

The output field previously built a TextEditingController inside build(), so
every rebuild — one per output line now that output streams — discarded and
replaced it. Replaced with SelectableText, which needs no controller.

Seeds the inputs from TestConfiguration's own defaults rather than a second
hardcoded server address, so there is one default to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:22 +09:00
mikkeli 3b01b0c4a6 fix(app): stream native output and release run state on every exit path
_isRunning was never cleared on the timeout path, so a single timed-out run
locked the app out of starting another until it was restarted. It is now
cleared in a finally block covering all three exits.

Output was buffered inside the service and only handed over once the run
finished, so the UI showed nothing until the end despite the native layer
streaming line by line. The service republishes each line on outputLines and
the view model appends to a transcript as they arrive.

The channel handler was re-registered on every run and dereferenced
_runCompleter with `!`, which threw once a late callback arrived after the
field had been nulled. It is registered once in the constructor and reads the
completer through a local.

Also drops the duplicated argument list in favour of
TestConfiguration.toCliArgs(), so the flags the UI sends have one definition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:22 +09:00
mikkeli f410a7e0d3 fix(android): run iperf3 tests off the Flutter platform thread
runIperfLive() blocks for the full duration of a test, but the MethodChannel
handler called it directly on the platform thread. That froze the UI for the
whole run, and because output reaches Dart by posting to the main looper, it
also meant no line could be delivered until the test was already over — the
live output the bridge streams had nowhere to go. Tests now run on a
dedicated single-thread executor and the channel call returns immediately.

MainThreadCallback's constructor properties were named onOutput/onError/
onComplete, the same names as the interface methods overriding them, so
`mainHandler.post { onOutput(line) }` resolved to the member function rather
than the lambda and re-posted itself instead of reaching the channel. Renamed
to emit* so the call cannot bind to the wrong declaration.

A failed System.loadLibrary now surfaces as a NATIVE_LIB_MISSING channel
error rather than leaving every later call to hang, and detaching from the
engine stops an in-flight test whose callbacks would fire into a dead
channel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:22 +09:00
mikkeli 2bba647a7b fix(jni): close the output pipe before joining the reader thread
Every test hung on the thread that started it. iperf_cleanup_and_signal()
called pthread_join() on the reader thread before closing the pipe's write
end, but the reader blocks in fgets() until EOF and EOF only arrives once
that write end is closed — two lines further down, after the join. Neither
side could ever make progress.

The reader also freed the shared state struct on exit while the caller went
on to read state->outfile and state->callback_ref and free it a second time,
so any path that got past the join corrupted the heap. Ownership now sits
solely with the JNI caller, which frees the struct after the join.

On the longjmp path outfile was fclose'd twice, once inline and once inside
the cleanup helper, so close_outfile() is idempotent and the hand-rolled
unwinding is gone. Signal handlers are restored to SIG_DFL once a test ends,
because leaving them armed pointed a later SIGTERM at a jmp_buf whose stack
frame no longer existed.

g_test is now guarded by a mutex and a second concurrent run is refused: a
stop request arrives on the platform thread while the test runs elsewhere,
which would otherwise race the worker retiring the pointer. Startup failures
report through onError/onComplete instead of returning silently, so Dart
resolves its completer rather than waiting out a timeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:22 +09:00
irene af26a1c385 fix(jni): set app cache dir as temp dir and wrap callbacks in Handler
CI / Analyze & Test (push) Failing after 43s
- Set app cache dir as temp directory for mkstemp() to avoid EACCES
  on Android sandbox (iperf_set_test_template).
- Wrap all MethodChannel callbacks in MainThreadCallback with
  Handler(Looper.getMainLooper()) to prevent SIGABRT from calling
  invokeMethod from native reader thread.
- Add setTempDir JNI function and logging for test return value.
2026-08-26 18:30:20 +09:00
irene a31cdef775 fix: resolve argv[0] misparse and SIGSEGV hang in JNI bridge
Two root causes prevented iperf3 tests from running:

1. argv[0] bug: iperf_parse_arguments() expects argv[0] to be the
   program name. We passed Dart args directly, so argv[0] was '-c',
   which iperf3 misinterpreted as the program name → 'Argument parse
   error: must either be a client (-c) or server (-s)'. Fixed by
   prepending "iperf3" as argv[0] and shifting user args to argv[1+].

2. Signal handling: iperf_catch_sigend() installs handlers that use
   longjmp() to a jmp_buf, but we never initialized the buffer with
   setjmp(). Any signal during iperf_run_client() caused SIGSEGV or
   hang. Added setjmp/longjmp wrapper matching main.c's pattern,
   SIGPIPE=SIG_IGN, and a 30s connect_timeout to avoid DNS hangs.

Also extracted iperf_cleanup_and_signal() to deduplicate the
normal-return and longjmp paths. Added diagnostic logging to
IperfRunner.kt's library loading for faster future debugging.
2026-08-26 01:52:37 +09:00
irene 7263808918 docs: add current structure/implementation/design documentation
CI / Analyze & Test (push) Failing after 27s
2026-08-25 19:25:40 +09:00
irene 0dd202291c fix: resolve SIGBUS crash in JNI bridge — use CallVoidMethod instead of function pointer casts
The root cause of the SIGBUS (alignment fault) crash was casting jmethodID
values to function pointers and invoking them directly. jmethodID is an
opaque JNI type that must only be used via CallVoidMethod/CallObjectMethod
etc. Calling it as a function pointer dereferences garbage memory, causing
SIGBUS at address 0xb1.

Changes:
- Replace function pointer typedefs (output_callback_t etc.) with a struct
  storing JNIEnv + callback global ref + jmethodIDs
- All callbacks now go through CallVoidMethod(env, ref, mid, ...)
- Remove getTestProgress() (never used, unused start_time workaround)
- Fix Kotlin arg cast: List<String> -> toTypedArray() (ClassCastException)
- Update UI default server to speedtest.uztelecom.uz
2026-08-25 18:59:42 +09:00
irene d551f41b41 feat: switch iperf3 to JNI with 3.19 source
- Switch from bundled iperf3 binary (Process.start) to JNI compilation
- iperf3 3.21 source API was incompatible (start_time, iperf_strerror, etc.)
- iperf3 3.19 source is proven to work (used by CellularLab)
- CMakeLists.txt updated: config.h generated into cpp/iperf/ for correct include paths
- iperf_jni.c fixed for 3.19 API (no start_time on iperf_test)
- getTestProgress() returns g_test->duration as fallback (no real-time elapsed tracking yet)
- NDK CMake pinned to 3.22.1
- UI layer updated: removed Iperf3BinaryPathProvider references
- Tests updated: TestRunViewModelTest matches new service interface
- pubspec.yaml: removed iperf3 assets bundle, added http dependency
- AndroidManifest: INTERNET permission (needed for hostname resolution)
2026-08-25 13:46:33 +09:00
irene 27e8e50469 feat: add INTERNET permission to AndroidManifest
Required for iperf3 client mode to resolve hostnames and connect to servers. Without this, the app silently fails to make network connections.
2026-08-24 13:11:37 +09:00
92 changed files with 27039 additions and 492 deletions
+55 -5
View File
@@ -3,20 +3,39 @@ name: CI
on:
push:
branches: [ master ]
paths:
- 'lib/**.dart'
- 'test/**.dart'
- 'android/**'
- 'tool/**'
- 'pubspec.yaml'
- 'pubspec.lock'
pull_request:
branches: [ master ]
workflow_dispatch:
# Both jobs target a self-hosted runner, by capability rather than by hostname.
# See tool/setup_ci_runner.sh for how that runner is provisioned and why it is
# not the Gitea host: Flutter cannot build an Android APK on a Linux arm64 host
# (flutter/flutter#189724), and the Gitea box is aarch64. The runner is
# registered at android-stuff org scope so any repo in the org can use it.
#
# The runner is host-mode, so Flutter, the JDK and the Android SDK/NDK are the
# machine's own — the same toolchain that produced the verified release, and no
# multi-hundred-MB download per run. Nothing here installs a toolchain; the
# Toolchain step fails loudly if one is missing.
jobs:
analyze:
name: Analyze & Test
runs-on: ubuntu-latest
runs-on: flutter-android
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.47.1'
channel: 'stable'
- name: Toolchain
run: |
flutter --version
echo "ANDROID_HOME=${ANDROID_HOME:?not set on this runner}"
- name: Get dependencies
run: flutter pub get
@@ -24,5 +43,36 @@ jobs:
- name: Analyze
run: flutter analyze
# Includes test/architecture_test.dart, which fails the build if a layer
# boundary is crossed.
- name: Run tests
run: flutter test
release-apk:
name: Release APK
runs-on: flutter-android
needs: [analyze]
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Get dependencies
run: flutter pub get
# The debug build does not minify, so it cannot catch an R8 problem. This
# job exists because a release APK that aborted on every test shipped
# once while the debug build passed every check.
- name: Build release APK
run: flutter build apk --release
# Building is not enough on its own: a renamed JNI callback still
# produces a successful build and a broken APK. This reads R8's mapping
# and the DEX string pool to confirm the names JNI resolves survived.
- name: Check JNI names survived minification
run: ./tool/check_jni_keep_rules.sh
- uses: actions/upload-artifact@v4
with:
name: app-release-apk
path: build/app/outputs/flutter-apk/app-release.apk
if-no-files-found: error
+26
View File
@@ -0,0 +1,26 @@
MIT License
Copyright (c) 2024–2026 Mikkeli Matlock
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Note: This project bundles iperf3 v3.19, which is licensed under the
BSD-3-Clause license. See the bundled iperf3 source for its full license text.
+42 -13
View File
@@ -1,13 +1,16 @@
# iperf3-gui
A Flutter-based Android GUI wrapper for iperf3, the popular network performance measurement tool.
A Flutter-based Android GUI wrapper for iperf3, the popular network performance
measurement tool.
## Features
- Client-mode network throughput testing
- Simple UI: enter server address, port, and press GO
- Raw iperf3 output display
- Bundled iperf3 v3.21 binaries for arm64-v8a and armeabi-v7a
- Configurable server, port, duration and parallel streams
- Live iperf3 output, streamed line by line while the test runs
- Stop a running test early
- Copy the transcript to the clipboard
- iperf3 3.19 compiled into the app as a native library — no external binary
## Architecture
@@ -15,33 +18,59 @@ A Flutter-based Android GUI wrapper for iperf3, the popular network performance
lib/
├── domain/
│ ├── models/ # TestConfiguration, Iperf3Result
│ └── services/ # Iperf3Service (Process runner)
├── data/
│ └── services/ # Iperf3Service (MethodChannel → JNI)
├── ui/
│ └── features/
│ └── test_run/ # TestRunScreen, TestRunViewModel
├── main.dart # App entry point
└── pubspec.yaml # Dependencies: provider, path_provider
└── main.dart # App entry point
android/app/src/main/
├── kotlin/…/IperfRunner.kt # MethodChannel handler, runs tests off the platform thread
└── cpp/iperf_jni.c # JNI bridge into the iperf3 API
```
See [docs/README.md](docs/README.md) for the data flow, the native API surface
and the design decisions behind them.
## Why JNI instead of executing a binary
Android SELinux in Enforcing mode blocks `execve()` from the `untrusted_app`
domain, so shipping an iperf3 ELF in assets and running it via `Process.start()`
fails with "Permission denied". iperf3 is therefore compiled as a shared library
(`libiperf3jni.so`) and called in-process over JNI.
## Building
Prerequisites:
- Flutter 3.47+
- Flutter 3.47+
- Android SDK with platform android-36
- JDK 17
- NDK r28+ and CMake 3.22.1 (for the native build)
```bash
flutter create . --org novoyuuparosk --project-name iperf3gui
flutter pub get
flutter analyze
flutter test
flutter build apk --debug
adb install -r build/app/outputs/flutter-apk/app-debug.apk
```
### iperf3 Binaries
## Testing against a local server
Pre-compiled binaries are sourced from [davidBar-On/android-iperf3](https://github.com/davidBar-On/android-iperf3)
and bundled in `assets/iperf3/` (arm64-v8a and armeabi-v7a for Android 28+).
```bash
iperf3 -s -p 5201 # on a machine on the same network
ip -4 addr show # note its address
```
Enter that address in the app, then press GO.
## Dependencies
- Dart: `provider`
- Native: iperf3 3.19 + cJSON, built by CMake for `armeabi-v7a` and `arm64-v8a`
## License
MIT — bundled iperf3 v3.21 is BSD-3-Clause licensed.
MIT — bundled iperf3 3.19 is BSD-3-Clause licensed.
+18 -1
View File
@@ -22,11 +22,14 @@ android {
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
// Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION
// is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions)
// is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-apk-versions)
// You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true`
// flag during build.
versionCode = flutter.versionCode
versionName = flutter.versionName
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
}
buildTypes {
@@ -34,6 +37,20 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
// proguard-rules.pro keeps the names JNI resolves at runtime.
// Without it a release build aborts on the first callback.
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
+21
View File
@@ -0,0 +1,21 @@
# JNI reaches these by name. R8 renames by default, so without these rules a
# release build resolves nothing: GetMethodID("onOutput", ...) returns NULL,
# raises NoSuchMethodError, and the next JNI call aborts the process with
# NoSuchMethodError: no non-static method "Lp0;.onOutput(Ljava/lang/String;)V"
# Debug builds do not minify, so this only ever appears in release.
# The plugin object holds the native method declarations and is looked up from
# C by its fully qualified name (Java_com_novoyuuparosk_iperf3gui_IperfRunner_*).
-keep class com.novoyuuparosk.iperf3gui.IperfRunner { *; }
# The callback interface and everything implementing it: iperf_jni.c resolves
# onOutput/onError/onComplete by name on the concrete object's class, so the
# implementors' method names must survive too, not just the interface's.
-keep interface com.novoyuuparosk.iperf3gui.IperfRunner$IperfCallback { *; }
-keep class * implements com.novoyuuparosk.iperf3gui.IperfRunner$IperfCallback { *; }
# Belt and braces for any future native method: keep the name of any class
# declaring one, and the native methods themselves.
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
+1
View File
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="iperf3_gui"
android:name="${applicationName}"
+74
View File
@@ -0,0 +1,74 @@
cmake_minimum_required(VERSION 3.22.1)
project("iperf3jni")
# ──────────────────────────────────────────────────────────────────
# Paths
# ──────────────────────────────────────────────────────────────────
# CMAKE_SOURCE_DIR here is android/app/src/main/cpp/
set(IPERF_SRC_DIR ${CMAKE_SOURCE_DIR}/iperf)
# ──────────────────────────────────────────────────────────────────
# Generate iperf_config.h from our Android template, into cpp/iperf/
# (iperf.h uses #include "iperf_config.h" which resolves relative to its dir)
# ──────────────────────────────────────────────────────────────────
configure_file(
${IPERF_SRC_DIR}/iperf_config_android.h
${IPERF_SRC_DIR}/iperf_config.h
)
# ──────────────────────────────────────────────────────────────────
# Shared library: iperf3jni
# ──────────────────────────────────────────────────────────────────
add_library(iperf3jni SHARED
# JNI bridge (directly in cpp/)
iperf_jni.c
# iperf3 source files (in cpp/iperf/)
iperf/iperf_api.c
iperf/iperf_client_api.c
iperf/iperf_util.c
iperf/iperf_udp.c
iperf/iperf_tcp.c
iperf/iperf_server_api.c
iperf/iperf_sctp.c
iperf/iperf_error.c
iperf/iperf_time.c
iperf/iperf_locale.c
iperf/iperf_pthread.c
iperf/iperf_auth.c
iperf/net.c
iperf/timer.c
iperf/units.c
iperf/cjson.c
iperf/dscp.c
iperf/tcp_info.c
)
# ──────────────────────────────────────────────────────────────────
# Include directories - iperf/ FIRST so "iperf_config.h" resolves there
# ──────────────────────────────────────────────────────────────────
target_include_directories(iperf3jni PRIVATE
${IPERF_SRC_DIR} # for iperf headers (iperf_config.h, iperf.h, etc.)
${CMAKE_SOURCE_DIR} # for JNI includes
)
# ──────────────────────────────────────────────────────────────────
# Compile definitions
# ──────────────────────────────────────────────────────────────────
target_compile_definitions(iperf3jni PRIVATE
HAVE_PTHREAD
__ANDROID__
# Disable the main() entry point — we're a shared lib, not a standalone binary
IPERF_JNI_BUILD=1
)
# ──────────────────────────────────────────────────────────────────
# Link libraries
# ──────────────────────────────────────────────────────────────────
find_package(Threads REQUIRED)
target_link_libraries(iperf3jni
Threads::Threads
log
android
)
+104
View File
@@ -0,0 +1,104 @@
lib_LTLIBRARIES = libiperf.la # Build and install an iperf library
bin_PROGRAMS = iperf3 # Build and install an iperf binary
if ENABLE_PROFILING
noinst_PROGRAMS = t_timer t_units t_uuid t_api t_auth iperf3_profile # Build, but don't install the test programs and a profiled version of iperf3
else
noinst_PROGRAMS = t_timer t_units t_uuid t_api t_auth # Build, but don't install the test programs
endif
include_HEADERS = iperf_api.h # Defines the headers that get installed with the program
# Specify the source files and flags for the iperf library
libiperf_la_SOURCES = \
cjson.c \
cjson.h \
flowlabel.h \
iperf.h \
iperf_api.c \
iperf_api.h \
iperf_error.c \
iperf_auth.h \
iperf_auth.c \
iperf_client_api.c \
iperf_locale.c \
iperf_locale.h \
iperf_server_api.c \
iperf_tcp.c \
iperf_tcp.h \
iperf_udp.c \
iperf_udp.h \
iperf_sctp.c \
iperf_sctp.h \
iperf_util.c \
iperf_util.h \
iperf_time.c \
iperf_time.h \
iperf_pthread.c \
iperf_pthread.h \
dscp.c \
net.c \
net.h \
portable_endian.h \
queue.h \
tcp_info.c \
timer.c \
timer.h \
units.c \
units.h \
version.h
# Specify the sources and various flags for the iperf binary
iperf3_SOURCES = main.c
iperf3_CFLAGS = -g
iperf3_LDADD = libiperf.la
iperf3_LDFLAGS = -g
if ENABLE_PROFILING
# If the iperf-profiled-binary is enabled
# Specify the sources and various flags for the profiled iperf binary. This
# binary recompiles all the source files to make sure they are all profiled.
iperf3_profile_SOURCES = main.c \
$(libiperf_la_SOURCES)
iperf3_profile_CFLAGS = -pg -g
iperf3_profile_LDADD = libiperf.la
iperf3_profile_LDFLAGS = -pg -g
endif
# Specify the sources and various flags for the test cases
t_timer_SOURCES = t_timer.c
t_timer_CFLAGS = -g
t_timer_LDFLAGS =
t_timer_LDADD = libiperf.la
t_units_SOURCES = t_units.c
t_units_CFLAGS = -g
t_units_LDFLAGS =
t_units_LDADD = libiperf.la
t_uuid_SOURCES = t_uuid.c
t_uuid_CFLAGS = -g
t_uuid_LDFLAGS =
t_uuid_LDADD = libiperf.la
t_api_SOURCES = t_api.c
t_api_CFLAGS = -g
t_api_LDFLAGS =
t_api_LDADD = libiperf.la
t_auth_SOURCES = t_auth.c
t_auth_CFLAGS = -g
t_auth_LDFLAGS =
t_auth_LDADD = libiperf.la
# Specify which tests to run during a "make check"
TESTS = \
t_timer \
t_units \
t_uuid \
t_api \
t_auth
dist_man_MANS = iperf3.1 libiperf.3
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 15
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int64_t valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif
+162
View File
@@ -0,0 +1,162 @@
/* dscp lookup routines lifted wholesale from openssh */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005,2006 Damien Miller. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <inttypes.h>
#ifdef WIN32
#define strcasecmp(a,b) _stricmp(a,b)
#define snprintf _snprintf
#endif
int parse_qos(const char *cp);
const char * iptos2str(int iptos);
/*
* Definitions for IP type of service (ip_tos)
*/
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifndef IPTOS_LOWDELAY
# define IPTOS_LOWDELAY 0x10
# define IPTOS_THROUGHPUT 0x08
# define IPTOS_RELIABILITY 0x04
# define IPTOS_LOWCOST 0x02
# define IPTOS_MINCOST IPTOS_LOWCOST
#endif /* IPTOS_LOWDELAY */
/*
* Definitions for DiffServ Codepoints as per RFC2474
*/
#ifndef IPTOS_DSCP_AF11
# define IPTOS_DSCP_AF11 0x28
# define IPTOS_DSCP_AF12 0x30
# define IPTOS_DSCP_AF13 0x38
# define IPTOS_DSCP_AF21 0x48
# define IPTOS_DSCP_AF22 0x50
# define IPTOS_DSCP_AF23 0x58
# define IPTOS_DSCP_AF31 0x68
# define IPTOS_DSCP_AF32 0x70
# define IPTOS_DSCP_AF33 0x78
# define IPTOS_DSCP_AF41 0x88
# define IPTOS_DSCP_AF42 0x90
# define IPTOS_DSCP_AF43 0x98
# define IPTOS_DSCP_EF 0xb8
#endif /* IPTOS_DSCP_AF11 */
#ifndef IPTOS_DSCP_CS0
# define IPTOS_DSCP_CS0 0x00
# define IPTOS_DSCP_CS1 0x20
# define IPTOS_DSCP_CS2 0x40
# define IPTOS_DSCP_CS3 0x60
# define IPTOS_DSCP_CS4 0x80
# define IPTOS_DSCP_CS5 0xa0
# define IPTOS_DSCP_CS6 0xc0
# define IPTOS_DSCP_CS7 0xe0
#endif /* IPTOS_DSCP_CS0 */
#ifndef IPTOS_DSCP_EF
# define IPTOS_DSCP_EF 0xb8
#endif /* IPTOS_DSCP_EF */
#ifndef IPTOS_DSCP_VA
# define IPTOS_DSCP_VA 0xb0
#endif /* IPTOS_DSCP_VA */
static const struct {
const char *name;
int value;
} ipqos[] = {
{ "af11", IPTOS_DSCP_AF11 },
{ "af12", IPTOS_DSCP_AF12 },
{ "af13", IPTOS_DSCP_AF13 },
{ "af21", IPTOS_DSCP_AF21 },
{ "af22", IPTOS_DSCP_AF22 },
{ "af23", IPTOS_DSCP_AF23 },
{ "af31", IPTOS_DSCP_AF31 },
{ "af32", IPTOS_DSCP_AF32 },
{ "af33", IPTOS_DSCP_AF33 },
{ "af41", IPTOS_DSCP_AF41 },
{ "af42", IPTOS_DSCP_AF42 },
{ "af43", IPTOS_DSCP_AF43 },
{ "cs0", IPTOS_DSCP_CS0 },
{ "cs1", IPTOS_DSCP_CS1 },
{ "cs2", IPTOS_DSCP_CS2 },
{ "cs3", IPTOS_DSCP_CS3 },
{ "cs4", IPTOS_DSCP_CS4 },
{ "cs5", IPTOS_DSCP_CS5 },
{ "cs6", IPTOS_DSCP_CS6 },
{ "cs7", IPTOS_DSCP_CS7 },
{ "ef", IPTOS_DSCP_EF },
{ "va", IPTOS_DSCP_VA },
{ "lowdelay", IPTOS_LOWDELAY },
{ "throughput", IPTOS_THROUGHPUT },
{ "reliability", IPTOS_RELIABILITY },
{ NULL, -1 }
};
int
parse_qos(const char *cp)
{
unsigned int i;
char *ep = NULL;
long val;
if (cp == NULL)
return -1;
for (i = 0; ipqos[i].name != NULL; i++) {
if (strcasecmp(cp, ipqos[i].name) == 0)
return ipqos[i].value;
}
/* Try parsing as an integer */
/* Max DSCP value is 2**6 - 1 */
val = strtol(cp, &ep, 0);
if (*cp == '\0' || *ep != '\0' || val < 0 || val > 63)
return -1;
return val << 2;
}
const char *
iptos2str(int iptos)
{
int i;
static char iptos_str[sizeof "0xff"];
if (iptos < 0 || iptos > 64) iptos = 0;
for (i = 0; ipqos[i].name != NULL; i++) {
if (ipqos[i].value == iptos)
return ipqos[i].name;
}
snprintf(iptos_str, sizeof iptos_str, "0x%02x", iptos);
return iptos_str;
}
@@ -0,0 +1,78 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __FLOW_LABEL_H
#define __FLOW_LABEL_H
#include <linux/types.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
#include <linux/in6.h>
#else
#ifndef __ANDROID__
/*
It is just a stripped copy of the Linux kernel header "linux/in6.h"
"Flow label" things are still not defined in "netinet/in*.h" headers,
but we cannot use "linux/in6.h" immediately because it currently
conflicts with "netinet/in.h" . (in kernel versions < 3.7.0)
*/
struct in6_flowlabel_req
{
struct in6_addr flr_dst;
__u32 flr_label;
__u8 flr_action;
__u8 flr_share;
__u16 flr_flags;
__u16 flr_expires;
__u16 flr_linger;
__u32 __flr_pad;
/* Options in format of IPV6_PKTOPTIONS */
};
#endif
#define IPV6_FL_A_GET 0
#define IPV6_FL_A_PUT 1
#define IPV6_FL_A_RENEW 2
#define IPV6_FL_F_CREATE 1
#define IPV6_FL_F_EXCL 2
#define IPV6_FL_S_NONE 0
#define IPV6_FL_S_EXCL 1
#define IPV6_FL_S_PROCESS 2
#define IPV6_FL_S_USER 3
#define IPV6_FL_S_ANY 255
#define IPV6_FLOWINFO_FLOWLABEL 0x000fffff
#define IPV6_FLOWINFO_PRIORITY 0x0ff00000
#endif
#define IPV6_FLOWLABEL_MGR 32
#define IPV6_FLOWINFO_SEND 33
#endif
+485
View File
@@ -0,0 +1,485 @@
/*
* iperf, Copyright (c) 2014-2020, 2023, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __IPERF_H
#define __IPERF_H
#include "iperf_config.h"
#include <sys/time.h>
#include <sys/types.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/select.h>
#include <sys/socket.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#ifdef HAVE_LINUX_TCP_H
#include <linux/tcp.h>
#else
#include <netinet/tcp.h>
#endif
#include <net/if.h> // for IFNAMSIZ
#if defined(HAVE_CPUSET_SETAFFINITY)
#include <sys/param.h>
#include <sys/cpuset.h>
#endif /* HAVE_CPUSET_SETAFFINITY */
#include "timer.h"
#include "queue.h"
#include "cjson.h"
#include "iperf_time.h"
#include "portable_endian.h"
#if defined(HAVE_SSL)
#include <openssl/bio.h>
#include <openssl/evp.h>
#endif // HAVE_SSL
#include "iperf_pthread.h"
/*
* Atomic types highly desired, but if not, we approximate what we need
* with normal integers and warn.
*/
#ifdef HAVE_STDATOMIC_H
#include <stdatomic.h>
#else
#warning "No <stdatomic.h> available."
typedef uint64_t atomic_uint_fast64_t;
#endif // HAVE_STDATOMIC_H
#if !defined(__IPERF_API_H)
typedef uint_fast64_t iperf_size_t;
typedef atomic_uint_fast64_t atomic_iperf_size_t;
#endif // __IPERF_API_H
#if (defined(__vxworks)) || (defined(__VXWORKS__))
typedef unsigned int uint
#endif // __vxworks or __VXWORKS__
struct iperf_sctp_info
{
long rtt;
long pmtu;
uint32_t wnd;
uint32_t cwnd;
};
struct iperf_interval_results
{
atomic_iperf_size_t bytes_transferred; /* bytes transferred in this interval */
struct iperf_time interval_start_time;
struct iperf_time interval_end_time;
float interval_duration;
/* for UDP */
int64_t interval_packet_count;
int64_t interval_outoforder_packets;
int64_t interval_cnt_error;
int64_t packet_count;
double jitter;
int64_t outoforder_packets;
int64_t cnt_error;
int omitted;
#if (defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) && \
defined(TCP_INFO)
struct tcp_info tcpInfo; /* getsockopt(TCP_INFO) for Linux, {Free,Net,Open}BSD */
#else
/* Just placeholders, never accessed. */
char *tcpInfo;
#endif
#if defined(HAVE_SCTP_H)
struct iperf_sctp_info sctp_info;
#endif /* HAVE_SCTP_H */
long interval_retrans;
long snd_cwnd;
long snd_wnd;
TAILQ_ENTRY(iperf_interval_results) irlistentries;
void *custom_data;
long rtt;
long rttvar;
long pmtu;
};
struct iperf_stream_result
{
atomic_iperf_size_t bytes_received;
atomic_iperf_size_t bytes_sent;
atomic_iperf_size_t bytes_received_this_interval;
atomic_iperf_size_t bytes_sent_this_interval;
atomic_iperf_size_t bytes_sent_omit;
long stream_prev_total_retrans;
long stream_retrans;
long stream_max_rtt;
long stream_min_rtt;
long stream_sum_rtt;
int stream_count_rtt;
long stream_max_snd_cwnd;
long stream_max_snd_wnd;
struct iperf_time start_time;
struct iperf_time end_time;
struct iperf_time start_time_fixed;
double sender_time;
double receiver_time;
TAILQ_HEAD(irlisthead, iperf_interval_results) interval_results;
void *data;
};
#define COOKIE_SIZE 37 /* size of an ascii uuid */
struct iperf_settings
{
int domain; /* AF_INET or AF_INET6 */
int socket_bufsize; /* window size for TCP */
int blksize; /* size of read/writes (-l) */
iperf_size_t rate; /* target data rate for application pacing*/
iperf_size_t bitrate_limit; /* server's maximum allowed total data rate for all streams*/
double bitrate_limit_interval; /* interval for averaging total data rate */
int bitrate_limit_stats_per_interval; /* calculated number of stats periods for averaging total data rate */
uint64_t fqrate; /* target data rate for FQ pacing*/
int pacing_timer; /* pacing timer in microseconds */
int burst; /* packets per burst */
int mss; /* for TCP MSS */
int ttl; /* IP TTL option */
int tos; /* type of service bit */
int flowlabel; /* IPv6 flow label */
iperf_size_t bytes; /* number of bytes to send */
iperf_size_t blocks; /* number of blocks (packets) to send */
char unit_format; /* -f */
int num_ostreams; /* SCTP initmsg settings */
int dont_fragment; /* Whether to set IP flag Do-Not_Fragment */
#if defined(HAVE_SSL)
char *authtoken; /* Authentication token */
char *client_username;
char *client_password;
EVP_PKEY *client_rsa_pubkey;
#endif // HAVE_SSL
int skip_rx_copy; /* Whether to ignore received messages data, using MSG_TRUNC option */
int connect_timeout; /* socket connection timeout, in ms */
int idle_timeout; /* server idle time timeout */
unsigned int snd_timeout; /* Timeout for sending tcp messages in active mode, in us */
struct iperf_time rcv_timeout; /* Timeout for receiving messages in active mode, in us */
int cntl_ka; /* Use Control TCP connection Keepalive */
int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */
int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */
int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */
};
struct iperf_test;
struct iperf_stream
{
struct iperf_test* test;
pthread_t thr;
int thread_created;
int done;
/* configurable members */
int local_port;
int remote_port;
int socket;
int id;
int sender;
/* XXX: is settings just a pointer to the same struct in iperf_test? if not,
should it be? */
struct iperf_settings *settings; /* pointer to structure settings */
/* non configurable members */
struct iperf_stream_result *result; /* structure pointer to result */
Timer *send_timer;
int green_light;
int buffer_fd; /* data to send, file descriptor */
char *buffer; /* data to send, mmapped */
int pending_size; /* pending data to send */
int diskfile_fd; /* file to send, file descriptor */
int diskfile_left; /* remaining file data on disk */
/*
* for udp measurements - This can be a structure outside stream, and
* stream can have a pointer to this
*/
int64_t packet_count;
int64_t peer_packet_count;
int64_t peer_omitted_packet_count;
int64_t omitted_packet_count;
double jitter;
double prev_transit;
int64_t outoforder_packets;
int64_t omitted_outoforder_packets;
int64_t cnt_error;
int64_t omitted_cnt_error;
uint64_t target;
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
int (*rcv) (struct iperf_stream * stream);
int (*snd) (struct iperf_stream * stream);
/* chained send/receive routines for -F mode */
int (*rcv2) (struct iperf_stream * stream);
int (*snd2) (struct iperf_stream * stream);
// struct iperf_stream *next;
SLIST_ENTRY(iperf_stream) streams;
void *data;
};
struct protocol {
int id;
char *name;
int (*accept)(struct iperf_test *);
int (*listen)(struct iperf_test *);
int (*connect)(struct iperf_test *);
int (*send)(struct iperf_stream *);
int (*recv)(struct iperf_stream *);
int (*init)(struct iperf_test *);
SLIST_ENTRY(protocol) protocols;
};
struct iperf_textline {
char *line;
TAILQ_ENTRY(iperf_textline) textlineentries;
};
struct xbind_entry {
char *name;
struct addrinfo *ai;
TAILQ_ENTRY(xbind_entry) link;
};
enum iperf_mode {
SENDER = 1,
RECEIVER = 0,
BIDIRECTIONAL = -1
};
enum debug_level {
DEBUG_LEVEL_ERROR = 1,
DEBUG_LEVEL_WARN = 2,
DEBUG_LEVEL_INFO = 3,
DEBUG_LEVEL_DEBUG = 4,
DEBUG_LEVEL_MAX = 4
};
struct iperf_test
{
pthread_mutex_t print_mutex;
char role; /* 'c' lient or 's' erver */
enum iperf_mode mode;
int sender_has_retransmits;
int other_side_has_retransmits; /* used if mode == BIDIRECTIONAL */
struct protocol *protocol;
signed char state;
char *server_hostname; /* -c option */
char *tmp_template;
char *bind_address; /* first -B option */
char *bind_dev; /* bind to network device */
TAILQ_HEAD(xbind_addrhead, xbind_entry) xbind_addrs; /* all -X opts */
int bind_port; /* --cport option */
int server_port;
int omit; /* duration of omit period (-O flag) */
int duration; /* total duration of test (-t flag) */
char *diskfile_name; /* -F option */
int affinity, server_affinity; /* -A option */
#if defined(HAVE_CPUSET_SETAFFINITY)
cpuset_t cpumask;
#endif /* HAVE_CPUSET_SETAFFINITY */
char *title; /* -T option */
char *extra_data; /* --extra-data */
char *congestion; /* -C option */
char *congestion_used; /* what was actually used */
char *remote_congestion_used; /* what the other side used */
char *pidfile; /* -P option */
char *logfile; /* --logfile option */
FILE *outfile;
int ctrl_sck;
int mapped_v4;
int listener;
int prot_listener;
int ctrl_sck_mss; /* MSS for the control channel */
#if defined(HAVE_SSL)
char *server_authorized_users;
EVP_PKEY *server_rsa_private_key;
int server_skew_threshold;
int use_pkcs1_padding;
#endif // HAVE_SSL
/* boolean variables for Options */
int daemon; /* -D option */
int one_off; /* -1 option */
int no_delay; /* -N option */
int reverse; /* -R option */
int bidirectional; /* --bidirectional */
int verbose; /* -V option - verbose mode */
int json_output; /* -J option - JSON output */
int json_stream; /* --json-stream */
void (*json_callback) (struct iperf_test *, char *); /* allow user apps to receive the
JSON strings,instead of writing them to the output file */
int zerocopy; /* -Z option - use sendfile */
int debug; /* -d option - enable debug */
enum debug_level debug_level; /* -d option option - level of debug messages to show */
int get_server_output; /* --get-server-output */
int udp_counters_64bit; /* --use-64-bit-udp-counters */
int forceflush; /* --forceflush - flushing output at every interval */
int multisend;
int repeating_payload; /* --repeating-payload */
int timestamps; /* --timestamps */
char *timestamp_format;
int mptcp; /* -m, --mptcp */
char *json_output_string; /* rendered JSON output if json_output is set */
/* Select related parameters */
int max_fd;
fd_set read_set; /* set of read sockets */
fd_set write_set; /* set of write sockets */
/* Interval related members */
int omitting;
double stats_interval;
double reporter_interval;
void (*stats_callback) (struct iperf_test *);
void (*reporter_callback) (struct iperf_test *);
Timer *omit_timer;
Timer *timer;
int done;
Timer *stats_timer;
Timer *reporter_timer;
double cpu_util[3]; /* cpu utilization of the test - total, user, system */
double remote_cpu_util[3]; /* cpu utilization for the remote host/client - total, user, system */
int num_streams; /* total streams in the test (-P) */
atomic_iperf_size_t bytes_sent;
atomic_iperf_size_t blocks_sent;
atomic_iperf_size_t bytes_received;
atomic_iperf_size_t blocks_received;
iperf_size_t bitrate_limit_stats_count; /* Number of stats periods accumulated for server's total bitrate average */
iperf_size_t *bitrate_limit_intervals_traffic_bytes; /* Pointer to a cyclic array that includes the last interval's bytes transferred */
iperf_size_t bitrate_limit_last_interval_index; /* Index of the last interval traffic inserted into the cyclic array */
int bitrate_limit_exceeded; /* Set by callback routine when average data rate exceeded the server's bitrate limit */
int server_last_run_rc; /* Save last server run rc for next test */
uint server_forced_idle_restarts_count; /* count number of forced server restarts to make sure it is not stack */
uint server_forced_no_msg_restarts_count; /* count number of forced server restarts to make sure it is not stack */
uint server_test_number; /* count number of tests performed by a server */
char cookie[COOKIE_SIZE];
// struct iperf_stream *streams; /* pointer to list of struct stream */
SLIST_HEAD(slisthead, iperf_stream) streams;
struct iperf_settings *settings;
SLIST_HEAD(plisthead, protocol) protocols;
/* callback functions */
void (*on_new_stream)(struct iperf_stream *);
void (*on_test_start)(struct iperf_test *);
void (*on_connect)(struct iperf_test *);
void (*on_test_finish)(struct iperf_test *);
/* cJSON handles for use when in -J mode */\
cJSON *json_top;
cJSON *json_start;
cJSON *json_connected;
cJSON *json_intervals;
cJSON *json_end;
/* Server output (use on client side only) */
char *server_output_text;
cJSON *json_server_output;
/* Server output (use on server side only) */
TAILQ_HEAD(iperf_textlisthead, iperf_textline) server_output_list;
};
/* default settings */
#define PORT 5201 /* default port to listen on (don't use the same port as iperf2) */
#define uS_TO_NS 1000
#define mS_TO_US 1000
#define SEC_TO_mS 1000
#define SEC_TO_US 1000000LL
#define UDP_RATE (1024 * 1024) /* 1 Mbps */
#define OMIT 0 /* seconds */
#define DURATION 10 /* seconds */
#define SEC_TO_NS 1000000000LL /* too big for enum/const on some platforms */
#define MAX_RESULT_STRING 4096
#define UDP_BUFFER_EXTRA 1024
#define MAX_PARAMS_JSON_STRING 8 * 1024
/* constants for command line arg sanity checks */
#define MB (1024 * 1024)
#define MAX_TCP_BUFFER (512 * MB)
#define MAX_BLOCKSIZE MB
/* Minimum size UDP send is the size of two 32-bit ints followed by a 64-bit int */
#define MIN_UDP_BLOCKSIZE (4 + 4 + 8)
/* Maximum size UDP send is (64K - 1) - IP and UDP header sizes */
#define MAX_UDP_BLOCKSIZE (65535 - 8 - 20)
#define MIN_INTERVAL 0.1
#define MAX_INTERVAL 60.0
#define MAX_TIME 86400
#define MAX_OMIT_TIME 600
#define MAX_BURST 1000
#define MAX_MSS (9 * 1024)
#define MAX_STREAMS 128
#define TIMESTAMP_FORMAT "%c "
extern int gerror; /* error value from getaddrinfo(3), for use in internal error handling */
/* UDP "connect" message and reply (textual value for Wireshark, etc. readability - legacy was numeric) */
#if BYTE_ORDER == BIG_ENDIAN
#define UDP_CONNECT_MSG 0x39383736
#define UDP_CONNECT_REPLY 0x36373839
#define LEGACY_UDP_CONNECT_REPLY 0xb168de3a
#else
#define UDP_CONNECT_MSG 0x36373839 // "6789" - legacy value was 123456789
#define UDP_CONNECT_REPLY 0x39383736 // "9876" - legacy value was 987654321
#define LEGACY_UDP_CONNECT_REPLY 987654321 // Old servers may still reply with the legacy value
#endif
/* In Reverse mode, maximum number of packets to wait for "accept" response - to handle out of order packets */
#define MAX_REVERSE_OUT_OF_ORDER_PACKETS 2
#endif /* !__IPERF_H */
+529
View File
@@ -0,0 +1,529 @@
.TH IPERF3 1 "May 2025" ESnet "User Manuals"
.SH NAME
iperf3 \- perform network throughput tests
.SH SYNOPSIS
.B iperf3 -s [
.I options
.B ]
.br
.B iperf3 -c
.I server
.B [
.I options
.B ]
.SH DESCRIPTION
iperf3 is a tool for performing network throughput measurements.
It can test TCP, UDP, or SCTP throughput.
To perform an iperf3 test the user must establish both a server and a
client.
.PP
The iperf3 executable contains both client and server functionality.
An iperf3 server can be started using either of the -s or
--server command-line parameters, for example:
.IP
\fCiperf3 -s\fR
.IP
\fCiperf3 --server \fR
.PP
Note that many iperf3 parameters have both short (-s) and long
(--server) forms.
In this section we will generally use the short form of command-line
flags, unless only the long form of a flag is available.
.PP
By default, the iperf3 server listens on TCP port 5201 for connections
from an iperf3 client.
A custom port can be specified by using the -p flag, for
example:
.IP
\fCiperf3 -s -p 5002\fR
.PP
After the server is started, it will listen for connections from
iperf3 clients (in other words, the iperf3 program run in client
mode).
The client mode can be started using the -c command-line option,
which also requires a host to which iperf3 should connect.
The host can by specified by hostname, IPv4 literal, or IPv6 literal:
.IP
\fCiperf3 -c iperf3.example.com\fR
.IP
\fCiperf3 -c 192.0.2.1\fR
.IP
\fCiperf3 -c 2001:db8::1\fR
.PP
If the iperf3 server is running on a non-default TCP port, that port
number needs to be specified on the client as well:
.IP
\fCiperf3 -c iperf3.example.com -p 5002\fR
.PP
The initial TCP connection is used to exchange test parameters,
control the start and end of the test, and to exchange test results.
This is sometimes referred to as the "control connection".
The actual test data is sent over a separate TCP connection, as a
separate flow of UDP packets, or as an independent SCTP connection,
depending on what protocol was specified by the client.
.PP
Normally, the test data is sent from the client to the server, and
measures the upload speed of the client.
Measuring the download speed from the server can be done by specifying
the -R flag on the client.
This causes data to be sent from the server to the client.
.IP
\fCiperf3 -c iperf3.example.com -p 5202 -R
.PP
Results are displayed on both the client and server.
There will be at least one line of output per measurement interval (by
default a measurement interval lasts for one second, but this can be
changed by the -i option).
Each line of output includes (at least) the time since the start of
the test, amount of data transferred during the interval, and the
average bitrate over that interval.
Note that the values for each measurement interval are taken from the
point of view of the endpoint process emitting that output (in other
words, the output on the client shows the measurement interval data for
the client.
.PP
At the end of the test is a set of statistics that shows (at
least as much as possible) a summary of the test as seen by both the
sender and the receiver, with lines tagged accordingly.
Recall that by default the client is the sender and the server is the
receiver, although as indicated above, use of the \fC-R\fR flag will
reverse these roles.
.PP
The client can be made to retrieve the server-side output for a given
test by specifying the --get-server-output flag.
.PP
Either the client or the server can produce its output in a JSON
structure, useful for integration with other programs, by passing it
the -J flag.
Normally the contents of the JSON structure are only completely known
after the test has finished, no JSON output will be emitted until the
end of the test.
By enabling line-delimited JSON multiple objects will be emitted to
provide a real-time parsable JSON output.
.PP
iperf3 has a (overly) large set of command-line options that can be
used to set the parameters of a test.
They are given in the "GENERAL OPTIONS" section of the manual page
below, as well as summarized in iperf3's help output, which can be
viewed by running iperf3 with the -h flag.
.SH "GENERAL OPTIONS"
.TP
.BR -p ", " --port " \fIn\fR"
set server port to listen on/connect to to \fIn\fR (default 5201)
.TP
.BR -f ", " --format " "
[kmgtKMGT] format to report: Kbits/Mbits/Gbits/Tbits
.TP
.BR -i ", " --interval " \fIn\fR"
pause \fIn\fR seconds between periodic throughput reports;
default is 1, use 0 to disable
.TP
.BR -I ", " --pidfile " \fIfile\fR"
write a file with the process ID, most useful when running as a daemon.
.TP
.BR -F ", " --file " \fIname\fR"
Use a file as the source (on the sender) or sink (on the receiver) of
data, rather than just generating random data or throwing it away.
This feature is used for finding whether or not the storage subsystem
is the bottleneck for file transfers.
It does not turn iperf3 into a file transfer tool.
The length, attributes, and in some cases contents of the received
file may not match those of the original file.
.TP
.BR -A ", " --affinity " \fIn/n,m\fR"
Set the CPU affinity, if possible (Linux, FreeBSD, and Windows only).
On both the client and server you can set the local affinity by using
the \fIn\fR form of this argument (where \fIn\fR is a CPU number).
In addition, on the client side you can override the server's
affinity for just that one test, using the \fIn,m\fR form of
argument.
Note that when using this feature, a process will only be bound
to a single CPU (as opposed to a set containing potentially multiple
CPUs).
.TP
.BR -B ", " --bind " \fIhost\fR[\fB%\fIdev\fR]"
bind to the specific interface associated with address \fIhost\fR.
If an optional interface is specified, it is treated as a shortcut
for \fB--bind-dev \fIdev\fR.
Note that a percent sign and interface device name are required for IPv6 link-local address literals.
.TP
.BR --bind-dev " \fIdev\fR"
bind to the specified network interface.
This option uses SO_BINDTODEVICE, and may require root permissions.
(Available on Linux and possibly other systems.)
.TP
.BR -V ", " --verbose " "
give more detailed output
.TP
.BR -J ", " --json " "
output in JSON format
.TP
.BR --json-stream " "
output in line-delimited JSON format
.TP
.BR --logfile " \fIfile\fR"
send output to a log file.
.TP
.BR --forceflush " "
force flushing output at every interval.
Used to avoid buffering when sending output to pipe.
.TP
.BR --timestamps "[\fB=\fIformat\fR]"
prepend a timestamp at the start of each output line.
By default, timestamps have the format emitted by
.BR ctime ( 1 ).
Optionally, \fC=\fR followed by
a format specification can be passed to customize the
timestamps, see
.BR strftime ( 3 ).
If this optional format is given, the \fC=\fR must immediately
follow the \fB--timestamps\fR option with no whitespace intervening.
.TP
.BR --rcv-timeout " \fI#\fR"
set idle timeout for receiving data during active tests. The receiver
will halt a test if no data is received from the sender for this
number of ms (default to 120000 ms, or 2 minutes).
.TP
.BR --snd-timeout " \fI#\fR"
set timeout for unacknowledged TCP data (on both test and control
connections) This option can be used to force a faster test timeout
in case of a network partition during a test. The required
parameter is specified in ms, and defaults to the system settings.
This functionality depends on the TCP_USER_TIMEOUT socket option, and
will not work on systems that do not support it.
.TP
.BR --use-pkcs1-padding
This option is only meaningful when using iperf3's authentication
features. Versions of iperf3 prior to 3.17 used PCKS1 padding in the
RSA-encrypted credentials, which was vulnerable to a side-channel
attack that could reveal a server's private key. Beginning with
iperf-3.17, OAEP padding is used, however this is a breaking change
that is not compatible with older iperf3 versions. Use this option to
preserve the less secure, but more compatible, behavior.
.TP
.BR -m ", " --mptcp " "
use mptcp variant for the current protocol. This only applies to
TCP and enables MPTCP usage.
.TP
.BR -d ", " --debug " "
emit debugging output.
Primarily (perhaps exclusively) of use to developers.
.TP
.BR -v ", " --version " "
show version information and quit
.TP
.BR -h ", " --help " "
show a help synopsis
.SH "SERVER SPECIFIC OPTIONS"
.TP
.BR -s ", " --server " "
run in server mode
.TP
.BR -D ", " --daemon " "
run the server in background as a daemon
.TP
.BR -1 ", " --one-off
handle one client connection, then exit. If an idle time is set, the
server will exit after that amount of time with no connection.
.TP
.BR --idle-timeout " \fIn\fR"
restart the server after \fIn\fR seconds in case it gets stuck. In
one-off mode, this is the number of seconds the server will wait
before exiting.
.TP
.BR --server-bitrate-limit " \fIn\fR[KMGT]"
set a limit on the server side, which will cause a test to abort if
the client specifies a test of more than \fIn\fR bits per second, or
if the average data sent or received by the client (including all data
streams) is greater than \fIn\fR bits per second. The default limit
is zero, which implies no limit. The interval over which to average
the data rate is 5 seconds by default, but can be specified by adding
a '/' and a number to the bitrate specifier.
.TP
.BR --rsa-private-key-path " \fIfile\fR"
path to the RSA private key (not password-protected) used to decrypt
authentication credentials from the client (if built with OpenSSL
support).
.TP
.BR --authorized-users-path " \fIfile\fR"
path to the configuration file containing authorized users credentials to run
iperf tests (if built with OpenSSL support).
The file is a comma separated list of usernames and password hashes;
more information on the structure of the file can be found in the
EXAMPLES section.
.TP
.BR --time-skew-threshold second " \fIseconds\fR"
time skew threshold (in seconds) between the server and client
during the authentication process.
.SH "CLIENT SPECIFIC OPTIONS"
.TP
.BR -c ", " --client " \fIhost\fR[\fB%\fIdev\fR]"
run in client mode, connecting to the specified server.
By default, a test consists of sending data from the client to the
server, unless the \-R flag is specified.
If an optional interface is specified, it is treated as a shortcut
for \fB--bind-dev \fIdev\fR.
Note that a percent sign and interface device name are required for IPv6 link-local address literals.
.TP
.BR --sctp
use SCTP rather than TCP (FreeBSD and Linux)
.TP
.BR -u ", " --udp
use UDP rather than TCP
.TP
.BR --connect-timeout " \fIn\fR"
set timeout for establishing the initial control connection to the
server, in milliseconds.
The default behavior is the operating system's timeout for TCP
connection establishment.
Providing a shorter value may speed up detection of a down iperf3
server.
.TP
.BR -b ", " --bitrate " \fIn\fR[KMGT]"
set target bitrate to \fIn\fR bits/sec (default 1 Mbit/sec for UDP,
unlimited for TCP/SCTP).
If there are multiple streams (\-P flag), the throughput limit is applied
separately to each stream.
You can also add a '/' and a number to the bitrate specifier.
This is called "burst mode".
It will send the given number of packets without pausing, even if that
temporarily exceeds the specified throughput limit.
Setting the target bitrate to 0 will disable bitrate limits
(particularly useful for UDP tests).
This throughput limit is implemented internally inside iperf3, and is
available on all platforms.
Compare with the \--fq-rate flag.
This option replaces the \--bandwidth flag, which is now deprecated
but (at least for now) still accepted.
.TP
.BR --pacing-timer " \fIn\fR[KMGT]"
set pacing timer interval in microseconds (default 1000 microseconds,
or 1 ms).
This controls iperf3's internal pacing timer for the \-b/\--bitrate
option.
The timer fires at the interval set by this parameter.
Smaller values of the pacing timer parameter smooth out the traffic
emitted by iperf3, but potentially at the cost of performance due to
more frequent timer processing.
.TP
.BR --fq-rate " \fIn\fR[KMGT]"
Set a rate to be used with fair-queueing based socket-level pacing,
in bits per second.
This pacing (if specified) will be in addition to any pacing due to
iperf3's internal throughput pacing (\-b/\--bitrate flag), and both can be
specified for the same test.
Only available on platforms supporting the
\fCSO_MAX_PACING_RATE\fR socket option (currently only Linux).
The default is no fair-queueing based pacing.
.TP
.BR --no-fq-socket-pacing
This option is deprecated and will be removed.
It is equivalent to specifying --fq-rate=0.
.TP
.BR -t ", " --time " \fIn\fR"
time in seconds to transmit for (default 10 secs)
.TP
.BR -n ", " --bytes " \fIn\fR[KMGT]"
number of bytes to transmit (instead of \-t)
.TP
.BR -k ", " --blockcount " \fIn\fR[KMGT]"
number of blocks (packets) to transmit (instead of \-t or \-n)
.TP
.BR -l ", " --length " \fIn\fR[KMGT]"
length of buffer to read or write. For TCP tests, the default value
is 128KB.
In the case of UDP, iperf3 tries to dynamically determine a reasonable
sending size based on the path MTU; if that cannot be determined it
uses 1460 bytes as a sending size.
For SCTP tests, the default size is 64KB.
.TP
.BR --cport " \fIport\fR"
bind data streams to a specific client port (for TCP and UDP only,
default is to use an ephemeral port)
.TP
.BR -P ", " --parallel " \fIn\fR"
number of parallel client streams to run. iperf3 will spawn off a
separate thread for each test stream. Using multiple streams may
result in higher throughput than a single stream.
.TP
.BR -R ", " --reverse
reverse the direction of a test, so that the server sends data to the
client
.TP
.BR --bidir
test in both directions (normal and reverse), with both the client and
server sending and receiving data simultaneously
.TP
.BR -w ", " --window " \fIn\fR[KMGT]"
set socket buffer size / window size.
This value gets sent to the server and used on that side too; on both
sides this option sets both the sending and receiving socket buffer sizes.
This option can be used to set (indirectly) the maximum TCP window size.
Note that on Linux systems, the effective maximum window size is approximately
double what is specified by this option (this behavior is not a bug in iperf3
but a "feature" of the Linux kernel, as documented by tcp(7) and socket(7)).
.TP
.BR -M ", " --set-mss " \fIn\fR"
set TCP/SCTP maximum segment size (MTU - 40 bytes)
.TP
.BR -N ", " --no-delay " "
set TCP/SCTP no delay, disabling Nagle's Algorithm
.TP
.BR -4 ", " --version4 " "
only use IPv4
.TP
.BR -6 ", " --version6 " "
only use IPv6
.TP
.BR -S ", " --tos " \fIn\fR"
set the IP type of service. The usual prefixes for octal and hex can be used,
i.e. 52, 064 and 0x34 all specify the same value.
.TP
.BR "--dscp " \fIdscp\fR
set the IP DSCP bits. Both numeric and symbolic values are accepted. Numeric
values can be specified in decimal, octal and hex (see --tos above).
.TP
.BR -L ", " --flowlabel " \fIn\fR"
set the IPv6 flow label (currently only supported on Linux)
.TP
.BR -X ", " --xbind " \fIname\fR"
Bind SCTP associations to a specific subset of links using sctp_bindx(3).
The \fB--B\fR flag will be ignored if this flag is specified.
Normally SCTP will include the protocol addresses of all active links
on the local host when setting up an association. Specifying at least
one \fB--X\fR name will disable this behaviour.
This flag must be specified for each link to be included in the
association, and is supported for both iperf servers and clients
(the latter are supported by passing the first \fB--X\fR argument to bind(2)).
Hostnames are accepted as arguments and are resolved using
getaddrinfo(3).
If the \fB--4\fR or \fB--6\fR flags are specified, names
which do not resolve to addresses within the
specified protocol family will be ignored.
.TP
.BR --nstreams " \fIn\fR"
Set number of SCTP streams.
.TP
.BR -Z ", " --zerocopy " "
Use a "zero copy" method of sending data, such as sendfile(2),
instead of the usual write(2).
.TP
.BR --skip-rx-copy
Ignored received packet data, using the MSG_TRUNC flag to the
recv(2) system call.
.TP
.BR -O ", " --omit " \fIn\fR"
Perform pre-test for N seconds and omit the pre-test statistics, to skip past the TCP slow-start
period.
.TP
.BR -T ", " --title " \fIstr\fR"
Prefix every output line with this string.
.TP
.BR --extra-data " \fIstr\fR"
Specify an extra data string field to be included in JSON output.
.TP
.BR -C ", " --congestion " \fIalgo\fR"
Set the congestion control algorithm (Linux and FreeBSD only). An
older
.B --linux-congestion
synonym for this flag is accepted but is deprecated.
.TP
.BR "--get-server-output"
Get the output from the server.
The output format is determined by the server (in particular, if the
server was invoked with the \fB--json\fR flag, the output will be in
JSON format, otherwise it will be in human-readable format).
If the client is run with \fB--json\fR, the server output is included
in a JSON object; otherwise it is appended at the bottom of the
human-readable output.
.TP
.BR --udp-counters-64bit
Use 64-bit counters in UDP test packets.
The use of this option can help prevent counter overflows during long
or high-bitrate UDP tests. Both client and server need to be running
at least version 3.1 for this option to work. It may become the
default behavior at some point in the future.
.TP
.BR --repeating-payload
Use repeating pattern in payload, instead of random bytes.
The same payload is used in iperf2 (ASCII '0..9' repeating).
It might help to test and reveal problems in networking gear with hardware
compression (including some WiFi access points), where iperf2 and iperf3
perform differently, just based on payload entropy.
.TP
.BR --dont-fragment
Set the IPv4 Don't Fragment (DF) bit on outgoing packets.
Only applicable to tests doing UDP over IPv4.
.TP
.BR --username " \fIusername\fR"
username to use for authentication to the iperf server (if built with
OpenSSL support).
The password will be prompted for interactively when the test is run. Note,
the password to use can also be specified via the IPERF3_PASSWORD environment
variable. If this variable is present, the password prompt will be skipped.
.TP
.BR --rsa-public-key-path " \fIfile\fR"
path to the RSA public key used to encrypt authentication credentials
(if built with OpenSSL support)
.SH EXAMPLES
.SS "Authentication - RSA Keypair"
The authentication feature of iperf3 requires an RSA public keypair.
The public key is used to encrypt the authentication token containing the
user credentials, while the private key is used to decrypt the authentication token.
The private key must be in PEM format and additionally must not have a
password set.
The public key must be in PEM format and use SubjectPrefixKeyInfo encoding.
An example of a set of UNIX/Linux commands using OpenSSL
to generate a correctly-formed keypair follows:
.sp 1
.in +.5i
> openssl genrsa -des3 -out private.pem 2048
.sp 0
> openssl rsa -in private.pem -outform PEM -pubout -out public.pem
.sp 0
> openssl rsa -in private.pem -out private_not_protected.pem -outform PEM
.in -.5i
.sp 1
After these commands, the public key will be contained in the file
public.pem and the private key will be contained in the file
private_not_protected.pem.
.SS "Authentication - Authorized users configuration file"
A simple plaintext file must be provided to the iperf3 server in order to specify
the authorized user credentials.
The file is a simple list of comma-separated pairs of a username and a
corresponding password hash.
The password hash is a SHA256 hash of the string "{$user}$password".
The file can also contain commented lines (starting with the \fC#\fR
character).
An example of commands to generate the password hash on a UNIX/Linux system
is given below:
.sp 1
.in +.5i
> S_USER=mario S_PASSWD=rossi
.sp 0
> echo -n "{$S_USER}$S_PASSWD" | sha256sum | awk '{ print $1 }'
.in -.5i
.sp 1
An example of a password file (with an entry corresponding to the
above username and password) is given below:
.sp 0
.in +.5i
> cat credentials.csv
.sp 0
# file format: username,sha256
.sp 0
mario,bf7a49a846d44b454a5d11e7acfaf13d138bbe0b7483aa3e050879700572709b
.in -.5i
.sp 1
.SH AUTHORS
A list of the contributors to iperf3 can be found within the
documentation located at
\fChttps://software.es.net/iperf/dev.html#authors\fR.
.SH "SEE ALSO"
libiperf(3),
https://software.es.net/iperf
File diff suppressed because it is too large Load Diff
+518
View File
@@ -0,0 +1,518 @@
/*
* iperf, Copyright (c) 2014-2025, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __IPERF_API_H
#define __IPERF_API_H
#include <sys/socket.h>
#include <sys/time.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" { /* open extern "C" */
#endif
/*
* Atomic types highly desired, but if not, we approximate what we need
* with normal integers and warn.
*/
#ifdef HAVE_STDATOMIC_H
#include <stdatomic.h>
#else
#warning "No <stdatomic.h> available"
typedef u_int64_t atomic_uint_fast64_t;
#endif // HAVE_STDATOMIC_H
struct iperf_test;
struct iperf_stream_result;
struct iperf_interval_results;
struct iperf_stream;
struct iperf_time;
#if !defined(__IPERF_H)
typedef uint_fast64_t iperf_size_t;
typedef atomic_uint_fast64_t atomic_iperf_size_t;
#endif // __IPERF_H
/* default settings */
#define Ptcp SOCK_STREAM
#define Pudp SOCK_DGRAM
#define Psctp 12
#define DEFAULT_UDP_BLKSIZE 1460 /* default is dynamically set, else this */
#define DEFAULT_TCP_BLKSIZE (128 * 1024) /* default read/write block size */
#define DEFAULT_SCTP_BLKSIZE (64 * 1024)
#define DEFAULT_PACING_TIMER 1000
#define DEFAULT_NO_MSG_RCVD_TIMEOUT 120000
#define MIN_NO_MSG_RCVD_TIMEOUT 100
#define WARN_STR_LEN 128
/* short option equivalents, used to support options that only have long form */
#define OPT_SCTP 1
#define OPT_LOGFILE 2
#define OPT_GET_SERVER_OUTPUT 3
#define OPT_UDP_COUNTERS_64BIT 4
#define OPT_CLIENT_PORT 5
#define OPT_NUMSTREAMS 6
#define OPT_FORCEFLUSH 7
#define OPT_NO_FQ_SOCKET_PACING 9 /* UNUSED */
#define OPT_FQ_RATE 10
#define OPT_DSCP 11
#define OPT_CLIENT_USERNAME 12
#define OPT_CLIENT_RSA_PUBLIC_KEY 13
#define OPT_SERVER_RSA_PRIVATE_KEY 14
#define OPT_SERVER_AUTHORIZED_USERS 15
#define OPT_PACING_TIMER 16
#define OPT_CONNECT_TIMEOUT 17
#define OPT_REPEATING_PAYLOAD 18
#define OPT_EXTRA_DATA 19
#define OPT_BIDIRECTIONAL 20
#define OPT_SERVER_BITRATE_LIMIT 21
#define OPT_TIMESTAMPS 22
#define OPT_SERVER_SKEW_THRESHOLD 23
#define OPT_BIND_DEV 24
#define OPT_IDLE_TIMEOUT 25
#define OPT_DONT_FRAGMENT 26
#define OPT_RCV_TIMEOUT 27
#define OPT_JSON_STREAM 28
#define OPT_SND_TIMEOUT 29
#define OPT_USE_PKCS1_PADDING 30
#define OPT_CNTL_KA 31
#define OPT_SKIP_RX_COPY 32
/* states */
#define TEST_START 1
#define TEST_RUNNING 2
#define RESULT_REQUEST 3 /* not used */
#define TEST_END 4
#define STREAM_BEGIN 5 /* not used */
#define STREAM_RUNNING 6 /* not used */
#define STREAM_END 7 /* not used */
#define ALL_STREAMS_END 8 /* not used */
#define PARAM_EXCHANGE 9
#define CREATE_STREAMS 10
#define SERVER_TERMINATE 11
#define CLIENT_TERMINATE 12
#define EXCHANGE_RESULTS 13
#define DISPLAY_RESULTS 14
#define IPERF_START 15
#define IPERF_DONE 16
#define ACCESS_DENIED (-1)
#define SERVER_ERROR (-2)
/* Getter routines for some fields inside iperf_test. */
int iperf_get_verbose( struct iperf_test* ipt );
int iperf_get_control_socket( struct iperf_test* ipt );
int iperf_get_test_omit( struct iperf_test* ipt );
int iperf_get_test_duration( struct iperf_test* ipt );
char iperf_get_test_role( struct iperf_test* ipt );
int iperf_get_test_reverse( struct iperf_test* ipt );
int iperf_get_test_bidirectional( struct iperf_test* ipt );
int iperf_get_test_blksize( struct iperf_test* ipt );
FILE* iperf_get_test_outfile( struct iperf_test* ipt );
uint64_t iperf_get_test_rate( struct iperf_test* ipt );
int iperf_get_test_pacing_timer( struct iperf_test* ipt );
uint64_t iperf_get_test_bytes( struct iperf_test* ipt );
uint64_t iperf_get_test_blocks( struct iperf_test* ipt );
int iperf_get_test_burst( struct iperf_test* ipt );
int iperf_get_test_socket_bufsize( struct iperf_test* ipt );
double iperf_get_test_reporter_interval( struct iperf_test* ipt );
double iperf_get_test_stats_interval( struct iperf_test* ipt );
int iperf_get_test_num_streams( struct iperf_test* ipt );
int iperf_get_test_repeating_payload( struct iperf_test* ipt );
int iperf_get_test_timestamps( struct iperf_test* ipt );
const char* iperf_get_test_timestamp_format( struct iperf_test* ipt );
int iperf_get_test_bind_port( struct iperf_test* ipt );
int iperf_get_test_server_port( struct iperf_test* ipt );
char* iperf_get_test_server_hostname( struct iperf_test* ipt );
char* iperf_get_test_template( struct iperf_test* ipt );
int iperf_get_test_protocol_id( struct iperf_test* ipt );
int iperf_get_test_json_output( struct iperf_test* ipt );
char* iperf_get_test_json_output_string ( struct iperf_test* ipt );
int iperf_get_test_json_stream( struct iperf_test* ipt );
int iperf_get_test_zerocopy( struct iperf_test* ipt );
int iperf_get_test_get_server_output( struct iperf_test* ipt );
char iperf_get_test_unit_format(struct iperf_test *ipt);
char* iperf_get_test_bind_address ( struct iperf_test* ipt );
char* iperf_get_test_bind_dev(struct iperf_test *ipt);
int iperf_get_test_udp_counters_64bit( struct iperf_test* ipt );
int iperf_get_test_one_off( struct iperf_test* ipt );
int iperf_get_test_tos( struct iperf_test* ipt );
char* iperf_get_extra_data( struct iperf_test* ipt );
char* iperf_get_iperf_version(void);
int iperf_get_test_no_delay( struct iperf_test* ipt );
int iperf_get_test_connect_timeout( struct iperf_test* ipt );
int iperf_get_dont_fragment( struct iperf_test* ipt );
char* iperf_get_test_congestion_control(struct iperf_test* ipt);
int iperf_get_test_mss(struct iperf_test* ipt);
int iperf_get_mapped_v4(struct iperf_test* ipt);
/* Setter routines for some fields inside iperf_test. */
void iperf_set_verbose( struct iperf_test* ipt, int verbose );
void iperf_set_control_socket( struct iperf_test* ipt, int ctrl_sck );
void iperf_set_test_omit( struct iperf_test* ipt, int omit );
void iperf_set_test_duration( struct iperf_test* ipt, int duration );
void iperf_set_test_reporter_interval( struct iperf_test* ipt, double reporter_interval );
void iperf_set_test_stats_interval( struct iperf_test* ipt, double stats_interval );
void iperf_set_test_state( struct iperf_test* ipt, signed char state );
void iperf_set_test_blksize( struct iperf_test* ipt, int blksize );
void iperf_set_test_logfile( struct iperf_test* ipt, const char *logfile );
void iperf_set_test_rate( struct iperf_test* ipt, uint64_t rate );
void iperf_set_test_pacing_timer( struct iperf_test* ipt, int pacing_timer );
void iperf_set_test_bytes( struct iperf_test* ipt, uint64_t bytes );
void iperf_set_test_blocks( struct iperf_test* ipt, uint64_t blocks );
void iperf_set_test_burst( struct iperf_test* ipt, int burst );
void iperf_set_test_bind_port( struct iperf_test* ipt, int bind_port );
void iperf_set_test_server_port( struct iperf_test* ipt, int server_port );
void iperf_set_test_socket_bufsize( struct iperf_test* ipt, int socket_bufsize );
void iperf_set_test_num_streams( struct iperf_test* ipt, int num_streams );
void iperf_set_test_repeating_payload( struct iperf_test* ipt, int repeating_payload );
void iperf_set_test_timestamps( struct iperf_test* ipt, int timestamps );
void iperf_set_test_timestamp_format( struct iperf_test*, const char *tf );
void iperf_set_test_role( struct iperf_test* ipt, char role );
void iperf_set_test_server_hostname( struct iperf_test* ipt, const char* server_hostname );
void iperf_set_test_template( struct iperf_test *ipt, const char *tmp_template );
void iperf_set_test_reverse( struct iperf_test* ipt, int reverse );
void iperf_set_test_json_output( struct iperf_test* ipt, int json_output );
void iperf_set_test_json_stream( struct iperf_test* ipt, int json_stream );
void iperf_set_test_json_callback(struct iperf_test *ipt, void (*callback)(struct iperf_test *, char *));
int iperf_has_zerocopy( void );
void iperf_set_test_zerocopy( struct iperf_test* ipt, int zerocopy );
void iperf_set_test_get_server_output( struct iperf_test* ipt, int get_server_output );
void iperf_set_test_unit_format(struct iperf_test *ipt, char unit_format);
void iperf_set_test_bind_address( struct iperf_test* ipt, const char *bind_address );
void iperf_set_test_bind_dev(struct iperf_test *ipt, const char *bnd_dev);
void iperf_set_test_udp_counters_64bit( struct iperf_test* ipt, int udp_counters_64bit );
void iperf_set_test_one_off( struct iperf_test* ipt, int one_off );
void iperf_set_test_tos( struct iperf_test* ipt, int tos );
void iperf_set_test_extra_data( struct iperf_test* ipt, const char *dat );
void iperf_set_test_bidirectional( struct iperf_test* ipt, int bidirectional);
void iperf_set_test_no_delay( struct iperf_test* ipt, int no_delay);
void iperf_set_dont_fragment( struct iperf_test* ipt, int dont_fragment );
void iperf_set_test_congestion_control(struct iperf_test* ipt, char* cc);
void iperf_set_test_mss(struct iperf_test* ipt, int mss);
void iperf_set_mapped_v4(struct iperf_test* ipt, const int val);
void iperf_set_on_new_stream_callback(struct iperf_test* ipt, void (*callback)(struct iperf_stream *));
void iperf_set_on_test_start_callback(struct iperf_test* ipt, void (*callback)(struct iperf_test *));
void iperf_set_on_test_connect_callback(struct iperf_test* ipt, void (*callback)(struct iperf_test *));
void iperf_set_on_test_finish_callback(struct iperf_test* ipt, void (*callback)(struct iperf_test *));
#if defined(HAVE_SSL)
void iperf_set_test_client_username(struct iperf_test *ipt, const char *client_username);
void iperf_set_test_client_password(struct iperf_test *ipt, const char *client_password);
void iperf_set_test_client_rsa_pubkey(struct iperf_test *ipt, const char *client_rsa_pubkey_base64);
void iperf_set_test_server_authorized_users(struct iperf_test *ipt, const char *server_authorized_users);
void iperf_set_test_server_skew_threshold(struct iperf_test *ipt, int server_skew_threshold);
void iperf_set_test_server_rsa_privkey(struct iperf_test *ipt, const char *server_rsa_privkey_base64);
#endif // HAVE_SSL
void iperf_set_test_connect_timeout(struct iperf_test *ipt, int ct);
/**
* exchange_parameters - handles the param_Exchange part for client
*
*/
int iperf_exchange_parameters(struct iperf_test * test);
/**
* add_to_interval_list -- adds new interval to the interval_list
*
*/
void add_to_interval_list(struct iperf_stream_result * rp, struct iperf_interval_results *temp);
/**
* connect_msg -- displays connection message
* denoting senfer/receiver details
*
*/
void connect_msg(struct iperf_stream * sp);
/**
* iperf_stats_callback -- handles the statistic gathering
*
*/
void iperf_stats_callback(struct iperf_test * test);
/**
* iperf_reporter_callback -- handles the report printing
*
*/
void iperf_reporter_callback(struct iperf_test * test);
/**
* iperf_new_test -- return a new iperf_test with default values
*
* returns NULL on failure
*
*/
struct iperf_test *iperf_new_test(void);
int iperf_defaults(struct iperf_test * testp);
/**
* iperf_free_test -- free resources used by test, calls iperf_free_stream to
* free streams
*
*/
void iperf_free_test(struct iperf_test * testp);
/**
* iperf_new_stream -- return a net iperf_stream with default values
*
* returns NULL on failure
*
*/
struct iperf_stream *iperf_new_stream(struct iperf_test *, int, int);
/**
* iperf_add_stream -- add a stream to a test
*
*/
void iperf_add_stream(struct iperf_test * test, struct iperf_stream * stream);
/**
* iperf_init_stream -- init resources associated with test
*
*/
int iperf_init_stream(struct iperf_stream *, struct iperf_test *);
/**
* iperf_free_stream -- free resources associated with test
*
*/
void iperf_free_stream(struct iperf_stream * sp);
/**
* iperf_common_sockopts -- init stream socket with common socket options
*
*/
int iperf_common_sockopts(struct iperf_test *, int s);
#if defined (HAVE_TCP_KEEPALIVE)
/**
* iperf_set_control_keepalive -- set control connection TCP keepalive
*
*/
int iperf_set_control_keepalive(struct iperf_test *test);
#endif //HAVE_TCP_KEEPALIVE
int has_tcpinfo(void);
int has_tcpinfo_retransmits(void);
void save_tcpinfo(struct iperf_stream *sp, struct iperf_interval_results *irp);
long get_total_retransmits(struct iperf_interval_results *irp);
long get_snd_cwnd(struct iperf_interval_results *irp);
long get_snd_wnd(struct iperf_interval_results *irp);
long get_rtt(struct iperf_interval_results *irp);
long get_rttvar(struct iperf_interval_results *irp);
long get_pmtu(struct iperf_interval_results *irp);
void print_tcpinfo(struct iperf_test *test);
void build_tcpinfo_message(struct iperf_interval_results *r, char *message);
int iperf_set_send_state(struct iperf_test *test, signed char state);
void iperf_check_throttle(struct iperf_stream *sp, struct iperf_time *nowP);
int iperf_send_mt(struct iperf_stream *) /* __attribute__((hot)) */;
int iperf_recv_mt(struct iperf_stream *);
void iperf_catch_sigend(void (*handler)(int));
void iperf_got_sigend(struct iperf_test *test, int sig) __attribute__ ((noreturn));
void usage(void);
void usage_long(FILE * f);
void warning(const char *);
int iperf_exchange_results(struct iperf_test *);
int iperf_init_test(struct iperf_test *);
int iperf_create_send_timers(struct iperf_test *);
int iperf_parse_arguments(struct iperf_test *, int, char **);
int iperf_open_logfile(struct iperf_test *);
void iperf_close_logfile(struct iperf_test *);
void iperf_reset_test(struct iperf_test *);
void iperf_reset_stats(struct iperf_test * test);
struct protocol *get_protocol(struct iperf_test *, int);
int set_protocol(struct iperf_test *, int);
void iperf_on_new_stream(struct iperf_stream *);
void iperf_on_test_start(struct iperf_test *);
void iperf_on_connect(struct iperf_test *);
void iperf_on_test_finish(struct iperf_test *);
extern jmp_buf env;
/* Client routines. */
int iperf_run_client(struct iperf_test *);
int iperf_connect(struct iperf_test *);
int iperf_create_streams(struct iperf_test *, int sender);
int iperf_handle_message_client(struct iperf_test *);
int iperf_client_end(struct iperf_test *);
/* Server routines. */
int iperf_run_server(struct iperf_test *);
int iperf_server_listen(struct iperf_test *);
int iperf_accept(struct iperf_test *);
int iperf_handle_message_server(struct iperf_test *);
int iperf_create_pidfile(struct iperf_test *);
int iperf_delete_pidfile(struct iperf_test *);
void iperf_check_total_rate(struct iperf_test *, iperf_size_t);
/* JSON output routines. */
int iperf_json_start(struct iperf_test *);
int iperf_json_finish(struct iperf_test *);
/* CPU affinity routines */
int iperf_setaffinity(struct iperf_test *, int affinity);
int iperf_clearaffinity(struct iperf_test *);
/* Custom printf routine. */
int iperf_printf(struct iperf_test *test, const char *format, ...) __attribute__ ((format(printf,2,3)));
int iflush(struct iperf_test *test);
/* Error routines. */
void iperf_err(struct iperf_test *test, const char *format, ...) __attribute__ ((format(printf,2,3)));
void iperf_errexit(struct iperf_test *test, const char *format, ...) __attribute__ ((format(printf,2,3),noreturn));
void iperf_signormalexit(struct iperf_test *test, const char *format, ...) __attribute__ ((format(printf,2,3),noreturn));
void iperf_exit(struct iperf_test *test, int exit_code, const char *format, va_list argp) __attribute__ ((noreturn));
char *iperf_strerror(int);
extern int i_errno;
enum {
IENONE = 0, // No error
/* Parameter errors */
IESERVCLIENT = 1, // Iperf cannot be both server and client
IENOROLE = 2, // Iperf must either be a client (-c) or server (-s)
IESERVERONLY = 3, // This option is server only
IECLIENTONLY = 4, // This option is client only
IEDURATION = 5, // test duration too long. Maximum value = %dMAX_TIME
IENUMSTREAMS = 6, // Number of parallel streams too large. Maximum value = %dMAX_STREAMS
IEBLOCKSIZE = 7, // Block size too large. Maximum value = %dMAX_BLOCKSIZE
IEBUFSIZE = 8, // Socket buffer size too large. Maximum value = %dMAX_TCP_BUFFER
IEINTERVAL = 9, // Invalid report interval (min = %gMIN_INTERVAL, max = %gMAX_INTERVAL seconds)
IEMSS = 10, // MSS too large. Maximum value = %dMAX_MSS
IENOSENDFILE = 11, // This OS does not support sendfile
IEOMIT = 12, // Bogus value for --omit
IEUNIMP = 13, // Not implemented yet
IEFILE = 14, // -F file couldn't be opened
IEBURST = 15, // Invalid burst count. Maximum value = %dMAX_BURST
IEENDCONDITIONS = 16, // Only one test end condition (-t, -n, -k) may be specified
IELOGFILE = 17, // Can't open log file
IENOSCTP = 18, // No SCTP support available
IEBIND = 19, // UNUSED: Local port specified with no local bind option
IEUDPBLOCKSIZE = 20, // Block size invalid
IEBADTOS = 21, // Bad TOS value
IESETCLIENTAUTH = 22, // Bad configuration of client authentication
IESETSERVERAUTH = 23, // Bad configuration of server authentication
IEBADFORMAT = 24, // Bad format argument to -f
IEREVERSEBIDIR = 25, // Iperf cannot be both reverse and bidirectional
IEBADPORT = 26, // Bad port number
IETOTALRATE = 27, // Total required bandwidth is larger than server's limit
IETOTALINTERVAL = 28, // Invalid time interval for calculating average data rate
IESKEWTHRESHOLD = 29, // Invalid value specified as skew threshold
IEIDLETIMEOUT = 30, // Invalid value specified as idle state timeout
IERCVTIMEOUT = 31, // Illegal message receive timeout
IERVRSONLYRCVTIMEOUT = 32, // Client receive timeout is valid only in reverse mode
IESNDTIMEOUT = 33, // Illegal message send timeout
IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
IESERVERAUTHUSERS = 35, // Cannot access authorized users file
IECNTLKA = 36, // Control connection Keepalive period should be larger than the full retry period (interval * count)
/* Test errors */
IENEWTEST = 100, // Unable to create a new test (check perror)
IEINITTEST = 101, // Test initialization failed (check perror)
IELISTEN = 102, // Unable to listen for connections (check perror)
IECONNECT = 103, // Unable to connect to server (check herror/perror) [from netdial]
IEACCEPT = 104, // Unable to accept connection from client (check herror/perror)
IESENDCOOKIE = 105, // Unable to send cookie to server (check perror)
IERECVCOOKIE = 106, // Unable to receive cookie from client (check perror)
IECTRLWRITE = 107, // Unable to write to the control socket (check perror)
IECTRLREAD = 108, // Unable to read from the control socket (check perror)
IECTRLCLOSE = 109, // Control socket has closed unexpectedly
IEMESSAGE = 110, // Received an unknown message
IESENDMESSAGE = 111, // Unable to send control message to client/server (check perror)
IERECVMESSAGE = 112, // Unable to receive control message from client/server (check perror)
IESENDPARAMS = 113, // Unable to send parameters to server (check perror)
IERECVPARAMS = 114, // Unable to receive parameters from client (check perror)
IEPACKAGERESULTS = 115, // Unable to package results (check perror)
IESENDRESULTS = 116, // Unable to send results to client/server (check perror)
IERECVRESULTS = 117, // Unable to receive results from client/server (check perror)
IESELECT = 118, // Select failed (check perror)
IECLIENTTERM = 119, // The client has terminated
IESERVERTERM = 120, // The server has terminated
IEACCESSDENIED = 121, // The server is busy running a test. Try again later.
IESETNODELAY = 122, // Unable to set TCP/SCTP NODELAY (check perror)
IESETMSS = 123, // Unable to set TCP/SCTP MSS (check perror)
IESETBUF = 124, // Unable to set socket buffer size (check perror)
IESETTOS = 125, // Unable to set IP TOS (check perror)
IESETCOS = 126, // Unable to set IPv6 traffic class (check perror)
IESETFLOW = 127, // Unable to set IPv6 flow label
IEREUSEADDR = 128, // Unable to set reuse address on socket (check perror)
IENONBLOCKING = 129, // Unable to set socket to non-blocking (check perror)
IESETWINDOWSIZE = 130, // Unable to set socket window size (check perror)
IEPROTOCOL = 131, // Protocol does not exist
IEAFFINITY = 132, // Unable to set CPU affinity (check perror)
IEDAEMON = 133, // Unable to become a daemon process
IESETCONGESTION = 134, // Unable to set TCP_CONGESTION
IEPIDFILE = 135, // Unable to write PID file
IEV6ONLY = 136, // Unable to set/unset IPV6_V6ONLY (check perror)
IESETSCTPDISABLEFRAG = 137, // Unable to set SCTP Fragmentation (check perror)
IESETSCTPNSTREAM= 138, // Unable to set SCTP number of streams (check perror)
IESETSCTPBINDX= 139, // Unable to process sctp_bindx() parameters
IESETPACING= 140, // Unable to set socket pacing rate
IESETBUF2= 141, // Socket buffer size incorrect (written value != read value)
IEAUTHTEST = 142, // Test authorization failed
IEBINDDEV = 143, // Unable to bind-to-device (check perror, maybe permissions?)
IENOMSG = 144, // No message was received for NO_MSG_RCVD_TIMEOUT time period
IESETDONTFRAGMENT = 145, // Unable to set IP Do-Not-Fragment
IEBINDDEVNOSUPPORT = 146, // `ip%%dev` is not supported as system does not support bind to device
IEHOSTDEV = 147, // host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address
IESETUSERTIMEOUT = 148, // Unable to set TCP USER_TIMEOUT (check perror)
IEPTHREADCREATE=150, // Unable to create thread (check perror)
IEPTHREADCANCEL=151, // Unable to cancel thread (check perror)
IEPTHREADJOIN=152, // Unable to join thread (check perror)
IEPTHREADATTRINIT=153, // Unable to initialize thread attribute (check perror)
IEPTHREADATTRDESTROY=154, // Unable to destroy thread attribute (check perror)
IESETCNTLKA = 155, // Unable to set socket keepalive (SO_KEEPALIVE) option
IESETCNTLKAKEEPIDLE = 156, // Unable to set socket keepalive TCP period (TCP_KEEPIDLE) option
IESETCNTLKAINTERVAL = 157, // Unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option
IESETCNTLKACOUNT = 158, // Unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option
IEPTHREADSIGMASK=159, // Unable to initialize sub thread signal mask (check perror)
/* Stream errors */
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)
IESTREAMLISTEN = 202, // Unable to start stream listener (check perror)
IESTREAMCONNECT = 203, // Unable to connect stream (check herror/perror)
IESTREAMACCEPT = 204, // Unable to accepte stream connection (check perror)
IESTREAMWRITE = 205, // Unable to write to stream socket (check perror)
IESTREAMREAD = 206, // Unable to read from stream (check perror)
IESTREAMCLOSE = 207, // Stream has closed unexpectedly
IESTREAMID = 208, // Stream has invalid ID
/* Timer errors */
IENEWTIMER = 300, // Unable to create new timer (check perror)
IEUPDATETIMER = 301, // Unable to update timer (check perror)
};
#ifdef __cplusplus
} /* close extern "C" */
#endif
#endif /* !__IPERF_API_H */
+455
View File
@@ -0,0 +1,455 @@
/*
* iperf, Copyright (c) 2014-2023, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE file
* for complete information.
*/
#include "iperf_config.h"
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/types.h>
/* FreeBSD needs _WITH_GETLINE to enable the getline() declaration */
#define _WITH_GETLINE
#include <stdio.h>
#include <termios.h>
#include <inttypes.h>
#include <stdint.h>
#if defined(HAVE_SSL)
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/buffer.h>
#include <openssl/err.h>
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/evp.h>
#include <openssl/core_names.h>
#endif
const char *auth_text_format = "user: %s\npwd: %s\nts: %"PRId64;
void sha256(const char *string, char outputBuffer[65])
{
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((const unsigned char *) string, strlen(string), hash);
int i = 0;
for(i = 0; i < SHA256_DIGEST_LENGTH; i++)
{
sprintf(outputBuffer + (i * 2), "%02x", hash[i]);
}
outputBuffer[64] = 0;
}
int check_authentication(const char *username, const char *password, const time_t ts, const char *filename, int skew_threshold){
time_t t = time(NULL);
time_t utc_seconds = mktime(localtime(&t));
if ( (utc_seconds - ts) > skew_threshold || (utc_seconds - ts) < -skew_threshold ) {
return 1;
}
char passwordHash[65];
char salted[strlen(username) + strlen(password) + 3];
sprintf(salted, "{%s}%s", username, password);
sha256(&salted[0], passwordHash);
char *s_username, *s_password;
int i;
FILE *ptr_file;
char buf[1024];
ptr_file =fopen(filename,"r");
if (!ptr_file)
return 2;
while (fgets(buf,1024, ptr_file)){
//strip the \n or \r\n chars
for (i = 0; buf[i] != '\0'; i++){
if (buf[i] == '\n' || buf[i] == '\r'){
buf[i] = '\0';
break;
}
}
//skip empty / not completed / comment lines
if (strlen(buf) == 0 || strchr(buf, ',') == NULL || buf[0] == '#'){
continue;
}
s_username = strtok(buf, ",");
s_password = strtok(NULL, ",");
if (strcmp( username, s_username ) == 0 && strcmp( passwordHash, s_password ) == 0){
fclose(ptr_file);
return 0;
}
}
fclose(ptr_file);
return 3;
}
int Base64Encode(const unsigned char* buffer, const size_t length, char** b64text) { //Encodes a binary safe base 64 string
BIO *bio, *b64;
BUF_MEM *bufferPtr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line
BIO_write(bio, buffer, length);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
*b64text = strndup( (*bufferPtr).data, (*bufferPtr).length );
BIO_free_all(bio);
return (0); //success
}
size_t calcDecodeLength(const char* b64input) { //Calculates the length of a decoded string
size_t len = strlen(b64input), padding = 0;
if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
padding = 2;
else if (b64input[len-1] == '=') //last char is =
padding = 1;
return (len*3)/4 - padding;
}
int Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) { //Decodes a base64 encoded string
BIO *bio, *b64;
int decodeLen = calcDecodeLength(b64message);
*buffer = (unsigned char*)malloc(decodeLen + 1);
(*buffer)[decodeLen] = '\0';
bio = BIO_new_mem_buf(b64message, -1);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer
*length = BIO_read(bio, *buffer, strlen(b64message));
assert(*length == decodeLen); //length should equal decodeLen, else something went horribly wrong
BIO_free_all(bio);
return (0); //success
}
EVP_PKEY *load_pubkey_from_file(const char *file) {
BIO *key = NULL;
EVP_PKEY *pkey = NULL;
if (file) {
key = BIO_new_file(file, "r");
if (key != NULL) {
pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
BIO_free(key);
}
}
return (pkey);
}
EVP_PKEY *load_pubkey_from_base64(const char *buffer) {
unsigned char *key = NULL;
size_t key_len;
Base64Decode(buffer, &key, &key_len);
BIO* bio = BIO_new(BIO_s_mem());
BIO_write(bio, key, key_len);
free(key);
EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
BIO_free(bio);
return (pkey);
}
EVP_PKEY *load_privkey_from_file(const char *file) {
BIO *key = NULL;
EVP_PKEY *pkey = NULL;
if (file) {
key = BIO_new_file(file, "r");
if (key != NULL) {
pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
BIO_free(key);
}
}
return (pkey);
}
EVP_PKEY *load_privkey_from_base64(const char *buffer) {
unsigned char *key = NULL;
size_t key_len;
Base64Decode(buffer, &key, &key_len);
BIO* bio = BIO_new(BIO_s_mem());
BIO_write(bio, key, key_len);
free(key);
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
BIO_free(bio);
return (pkey);
}
int test_load_pubkey_from_file(const char *file){
EVP_PKEY *key = load_pubkey_from_file(file);
if (key == NULL){
return -1;
}
EVP_PKEY_free(key);
return 0;
}
int test_load_private_key_from_file(const char *file){
EVP_PKEY *key = load_privkey_from_file(file);
if (key == NULL){
return -1;
}
EVP_PKEY_free(key);
return 0;
}
int encrypt_rsa_message(const char *plaintext, EVP_PKEY *public_key, unsigned char **encryptedtext, int use_pkcs1_padding) {
#if OPENSSL_VERSION_MAJOR >= 3
EVP_PKEY_CTX *ctx;
#else
RSA *rsa = NULL;
#endif
unsigned char *rsa_buffer = NULL;
size_t encryptedtext_len = 0;
int rsa_buffer_len, keysize;
#if OPENSSL_VERSION_MAJOR >= 3
int rc;
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, public_key, "");
/* See evp_pkey_rsa(7) and provider-keymgmt(7) */
rc = EVP_PKEY_get_int_param(public_key, OSSL_PKEY_PARAM_MAX_SIZE, &keysize); /* XXX not really keysize */
if (!rc) {
goto errreturn;
}
#else
rsa = EVP_PKEY_get1_RSA(public_key);
keysize = RSA_size(rsa);
#endif
rsa_buffer = OPENSSL_malloc(keysize * 2);
*encryptedtext = (unsigned char*)OPENSSL_malloc(keysize);
BIO *bioBuff = BIO_new_mem_buf((void*)plaintext, (int)strlen(plaintext));
rsa_buffer_len = BIO_read(bioBuff, rsa_buffer, keysize * 2);
int padding = RSA_PKCS1_OAEP_PADDING;
if (use_pkcs1_padding){
padding = RSA_PKCS1_PADDING;
}
#if OPENSSL_VERSION_MAJOR >= 3
EVP_PKEY_encrypt_init(ctx);
EVP_PKEY_CTX_set_rsa_padding(ctx, padding);
EVP_PKEY_encrypt(ctx, *encryptedtext, &encryptedtext_len, rsa_buffer, rsa_buffer_len);
EVP_PKEY_CTX_free(ctx);
#else
encryptedtext_len = RSA_public_encrypt(rsa_buffer_len, rsa_buffer, *encryptedtext, rsa, padding);
RSA_free(rsa);
#endif
OPENSSL_free(rsa_buffer);
BIO_free(bioBuff);
if (encryptedtext_len <= 0) {
goto errreturn;
}
return encryptedtext_len;
errreturn:
fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL));
return 0;
}
int decrypt_rsa_message(const unsigned char *encryptedtext, const int encryptedtext_len, EVP_PKEY *private_key, unsigned char **plaintext, int use_pkcs1_padding) {
#if OPENSSL_VERSION_MAJOR >= 3
EVP_PKEY_CTX *ctx;
#else
RSA *rsa = NULL;
#endif
unsigned char *rsa_buffer = NULL;
size_t plaintext_len = 0;
int rsa_buffer_len, keysize;
#if OPENSSL_VERSION_MAJOR >= 3
int rc;
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, private_key, "");
/* See evp_pkey_rsa(7) and provider-keymgmt(7) */
rc = EVP_PKEY_get_int_param(private_key, OSSL_PKEY_PARAM_MAX_SIZE, &keysize); /* XXX not really keysize */
if (!rc) {
goto errreturn;
}
#else
rsa = EVP_PKEY_get1_RSA(private_key);
keysize = RSA_size(rsa);
#endif
rsa_buffer = OPENSSL_malloc(keysize * 2);
*plaintext = (unsigned char*)OPENSSL_malloc(keysize);
BIO *bioBuff = BIO_new_mem_buf((void*)encryptedtext, encryptedtext_len);
rsa_buffer_len = BIO_read(bioBuff, rsa_buffer, keysize * 2);
int padding = RSA_PKCS1_OAEP_PADDING;
if (use_pkcs1_padding){
padding = RSA_PKCS1_PADDING;
}
#if OPENSSL_VERSION_MAJOR >= 3
plaintext_len = keysize;
EVP_PKEY_decrypt_init(ctx);
int ret = EVP_PKEY_CTX_set_rsa_padding(ctx, padding);
if (ret < 0){
goto errreturn;
}
EVP_PKEY_decrypt(ctx, *plaintext, &plaintext_len, rsa_buffer, rsa_buffer_len);
EVP_PKEY_CTX_free(ctx);
#else
plaintext_len = RSA_private_decrypt(rsa_buffer_len, rsa_buffer, *plaintext, rsa, padding);
RSA_free(rsa);
#endif
OPENSSL_free(rsa_buffer);
BIO_free(bioBuff);
/* Treat a decryption error as an empty string. */
if (plaintext_len < 0) {
plaintext_len = 0;
}
return plaintext_len;
errreturn:
fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL));
return 0;
}
int encode_auth_setting(const char *username, const char *password, EVP_PKEY *public_key, char **authtoken, int use_pkcs1_padding){
time_t t = time(NULL);
time_t utc_seconds = mktime(localtime(&t));
/*
* Compute a pessimistic/conservative estimate of storage required.
* It's OK to allocate too much storage but too little is bad.
*/
const int text_len = strlen(auth_text_format) + strlen(username) + strlen(password) + 32;
char *text = (char *) calloc(text_len, sizeof(char));
if (text == NULL) {
return -1;
}
snprintf(text, text_len, auth_text_format, username, password, (int64_t)utc_seconds);
unsigned char *encrypted = NULL;
int encrypted_len;
encrypted_len = encrypt_rsa_message(text, public_key, &encrypted, use_pkcs1_padding);
free(text);
if (encrypted_len < 0) {
return -1;
}
Base64Encode(encrypted, encrypted_len, authtoken);
OPENSSL_free(encrypted);
return (0); //success
}
int decode_auth_setting(int enable_debug, const char *authtoken, EVP_PKEY *private_key, char **username, char **password, time_t *ts, int use_pkcs1_padding){
unsigned char *encrypted_b64 = NULL;
size_t encrypted_len_b64;
int64_t utc_seconds;
Base64Decode(authtoken, &encrypted_b64, &encrypted_len_b64);
unsigned char *plaintext = NULL;
int plaintext_len;
plaintext_len = decrypt_rsa_message(encrypted_b64, encrypted_len_b64, private_key, &plaintext, use_pkcs1_padding);
free(encrypted_b64);
if (plaintext_len < 0) {
return -1;
}
plaintext[plaintext_len] = '\0';
char *s_username, *s_password;
s_username = (char *) calloc(plaintext_len, sizeof(char));
if (s_username == NULL) {
return -1;
}
s_password = (char *) calloc(plaintext_len, sizeof(char));
if (s_password == NULL) {
free(s_username);
return -1;
}
int rc = sscanf((char *) plaintext, auth_text_format, s_username, s_password, &utc_seconds);
if (rc != 3) {
free(s_password);
free(s_username);
return -1;
}
if (enable_debug) {
printf("Auth Token Content:\n%s\n", plaintext);
printf("Auth Token Credentials:\n--> %s %s\n", s_username, s_password);
}
*username = s_username;
*password = s_password;
*ts = (time_t)utc_seconds;
OPENSSL_free(plaintext);
return (0);
}
#endif //HAVE_SSL
ssize_t iperf_getpass (char **lineptr, size_t *n, FILE *stream) {
struct termios old, new;
ssize_t nread;
/* Turn echoing off and fail if we can't. */
if (tcgetattr (fileno (stream), &old) != 0)
return -1;
new = old;
new.c_lflag &= ~ECHO;
if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
return -1;
/* Read the password. */
printf("Password: ");
nread = getline (lineptr, n, stream);
/* Restore terminal. */
(void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
//strip the \n or \r\n chars
char *buf = *lineptr;
int i;
for (i = 0; buf[i] != '\0'; i++){
if (buf[i] == '\n' || buf[i] == '\r'){
buf[i] = '\0';
break;
}
}
return nread;
}
@@ -0,0 +1,41 @@
/*
* iperf, Copyright (c) 2014-2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE file
* for complete information.
*/
#include <time.h>
#include <sys/types.h>
#include <openssl/bio.h>
int test_load_pubkey_from_file(const char *public_keyfile);
int test_load_private_key_from_file(const char *private_keyfile);
EVP_PKEY *load_pubkey_from_file(const char *file);
EVP_PKEY *load_pubkey_from_base64(const char *buffer);
EVP_PKEY *load_privkey_from_file(const char *file);
EVP_PKEY *load_privkey_from_base64(const char *buffer);
int encode_auth_setting(const char *username, const char *password, EVP_PKEY *public_key, char **authtoken, int use_pkcs1_padding);
int decode_auth_setting(int enable_debug, const char *authtoken, EVP_PKEY *private_key, char **username, char **password, time_t *ts, int use_pkcs1_padding);
int check_authentication(const char *username, const char *password, const time_t ts, const char *filename, int skew_threshold);
ssize_t iperf_getpass (char **lineptr, size_t *n, FILE *stream);
@@ -0,0 +1,882 @@
/*
* iperf, Copyright (c) 2014-2023, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <signal.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_util.h"
#include "iperf_locale.h"
#include "iperf_time.h"
#include "net.h"
#include "timer.h"
#if defined(HAVE_TCP_CONGESTION)
#if !defined(TCP_CA_NAME_MAX)
#define TCP_CA_NAME_MAX 16
#endif /* TCP_CA_NAME_MAX */
#endif /* HAVE_TCP_CONGESTION */
void *
iperf_client_worker_run(void *s) {
struct iperf_stream *sp = (struct iperf_stream *) s;
struct iperf_test *test = sp->test;
/* Blocking signal to make sure that signal will be handled by main thread */
sigset_t set;
sigemptyset(&set);
#ifdef SIGTERM
sigaddset(&set, SIGTERM);
#endif
#ifdef SIGHUP
sigaddset(&set, SIGHUP);
#endif
#ifdef SIGINT
sigaddset(&set, SIGINT);
#endif
if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {
i_errno = IEPTHREADSIGMASK;
goto cleanup_and_fail;
}
/* Allow this thread to be cancelled even if it's in a syscall */
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (! (test->done) && ! (sp->done)) {
if (sp->sender) {
if (iperf_send_mt(sp) < 0) {
goto cleanup_and_fail;
}
}
else {
if (iperf_recv_mt(sp) < 0) {
goto cleanup_and_fail;
}
}
}
return NULL;
cleanup_and_fail:
return NULL;
}
int
iperf_create_streams(struct iperf_test *test, int sender)
{
if (NULL == test)
{
iperf_err(NULL, "No test\n");
return -1;
}
int i, s;
#if defined(HAVE_TCP_CONGESTION)
int saved_errno;
#endif /* HAVE_TCP_CONGESTION */
struct iperf_stream *sp;
int orig_bind_port = test->bind_port;
for (i = 0; i < test->num_streams; ++i) {
test->bind_port = orig_bind_port;
if (orig_bind_port) {
test->bind_port += i;
// If Bidir make sure send and receive ports are different
if (!sender && test->mode == BIDIRECTIONAL)
test->bind_port += test->num_streams;
}
s = test->protocol->connect(test);
test->bind_port = orig_bind_port;
if (s < 0)
return -1;
#if defined(HAVE_TCP_CONGESTION)
if (test->protocol->id == Ptcp) {
if (test->congestion) {
if (setsockopt(s, IPPROTO_TCP, TCP_CONGESTION, test->congestion, strlen(test->congestion)) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
i_errno = IESETCONGESTION;
return -1;
}
}
{
socklen_t len = TCP_CA_NAME_MAX;
char ca[TCP_CA_NAME_MAX + 1];
int rc;
rc = getsockopt(s, IPPROTO_TCP, TCP_CONGESTION, ca, &len);
if (rc < 0 && test->congestion) {
saved_errno = errno;
close(s);
errno = saved_errno;
i_errno = IESETCONGESTION;
return -1;
}
if (test->congestion_used) {
if (test->debug)
printf("Overriding existing congestion algorithm: %s\n", test->congestion_used);
free(test->congestion_used);
}
// Set actual used congestion alg, or set to unknown if could not get it
if (rc < 0)
test->congestion_used = strdup("unknown");
else
test->congestion_used = strdup(ca);
if (test->debug) {
printf("Congestion algorithm is %s\n", test->congestion_used);
}
}
}
#endif /* HAVE_TCP_CONGESTION */
sp = iperf_new_stream(test, s, sender);
if (!sp)
return -1;
/* Perform the new stream callback */
if (test->on_new_stream)
test->on_new_stream(sp);
}
return 0;
}
static void
test_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
test->timer = NULL;
test->done = 1;
}
static void
client_stats_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
if (test->done)
return;
if (test->stats_callback)
test->stats_callback(test);
}
static void
client_reporter_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
if (test->done)
return;
if (test->reporter_callback)
test->reporter_callback(test);
}
static int
create_client_timers(struct iperf_test * test)
{
struct iperf_time now;
TimerClientData cd;
if (NULL == test)
{
iperf_err(NULL, "No test\n");
i_errno = IEINITTEST;
return -1;
}
if (iperf_time_now(&now) < 0) {
i_errno = IEINITTEST;
return -1;
}
cd.p = test;
test->timer = test->stats_timer = test->reporter_timer = NULL;
if (test->duration != 0) {
test->done = 0;
test->timer = tmr_create(&now, test_timer_proc, cd, ( test->duration + test->omit ) * SEC_TO_US, 0);
if (test->timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
if (test->stats_interval != 0) {
test->stats_timer = tmr_create(&now, client_stats_timer_proc, cd, test->stats_interval * SEC_TO_US, 1);
if (test->stats_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
if (test->reporter_interval != 0) {
test->reporter_timer = tmr_create(&now, client_reporter_timer_proc, cd, test->reporter_interval * SEC_TO_US, 1);
if (test->reporter_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
return 0;
}
static void
client_omit_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
test->omit_timer = NULL;
test->omitting = 0;
iperf_reset_stats(test);
if (test->verbose && !test->json_output && test->reporter_interval == 0)
iperf_printf(test, "%s", report_omit_done);
/* Reset the timers. */
if (test->stats_timer != NULL)
tmr_reset(nowP, test->stats_timer);
if (test->reporter_timer != NULL)
tmr_reset(nowP, test->reporter_timer);
}
static int
create_client_omit_timer(struct iperf_test * test)
{
struct iperf_time now;
TimerClientData cd;
if (NULL == test)
{
iperf_err(NULL, "No test\n");
return -1;
}
if (test->omit == 0) {
test->omit_timer = NULL;
test->omitting = 0;
} else {
if (iperf_time_now(&now) < 0) {
i_errno = IEINITTEST;
return -1;
}
test->omitting = 1;
cd.p = test;
test->omit_timer = tmr_create(&now, client_omit_timer_proc, cd, test->omit * SEC_TO_US, 0);
if (test->omit_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
return 0;
}
int
iperf_handle_message_client(struct iperf_test *test)
{
int rval;
int32_t err;
if (NULL == test)
{
iperf_err(NULL, "No test\n");
i_errno = IEINITTEST;
return -1;
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Reading new State from the Server - current state is %d-%s\n", test->state, state_to_text(test->state));
}
/*!!! Why is this read() and not Nread()? */
if ((rval = read(test->ctrl_sck, (char*) &test->state, sizeof(signed char))) <= 0) {
if (rval == 0) {
i_errno = IECTRLCLOSE;
return -1;
} else {
i_errno = IERECVMESSAGE;
return -1;
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "State change: client received and changed State to %d-%s\n", test->state, state_to_text(test->state));
}
switch (test->state) {
case PARAM_EXCHANGE:
if (iperf_exchange_parameters(test) < 0)
return -1;
if (test->on_connect)
test->on_connect(test);
break;
case CREATE_STREAMS:
if (test->mode == BIDIRECTIONAL)
{
if (iperf_create_streams(test, 1) < 0)
return -1;
if (iperf_create_streams(test, 0) < 0)
return -1;
}
else if (iperf_create_streams(test, test->mode) < 0)
return -1;
break;
case TEST_START:
if (iperf_init_test(test) < 0)
return -1;
if (create_client_timers(test) < 0)
return -1;
if (create_client_omit_timer(test) < 0)
return -1;
if (test->mode)
if (iperf_create_send_timers(test) < 0)
return -1;
break;
case TEST_RUNNING:
break;
case EXCHANGE_RESULTS:
if (iperf_exchange_results(test) < 0)
return -1;
break;
case DISPLAY_RESULTS:
if (test->on_test_finish)
test->on_test_finish(test);
iperf_client_end(test);
break;
case IPERF_DONE:
break;
case SERVER_TERMINATE:
i_errno = IESERVERTERM;
/*
* Temporarily be in DISPLAY_RESULTS phase so we can get
* ending summary statistics.
*/
signed char oldstate = test->state;
cpu_util(test->cpu_util);
test->state = DISPLAY_RESULTS;
test->reporter_callback(test);
test->state = oldstate;
return -1;
case ACCESS_DENIED:
i_errno = IEACCESSDENIED;
return -1;
case SERVER_ERROR:
if (Nread(test->ctrl_sck, (char*) &err, sizeof(err), Ptcp) < 0) {
i_errno = IECTRLREAD;
return -1;
}
i_errno = ntohl(err);
if (Nread(test->ctrl_sck, (char*) &err, sizeof(err), Ptcp) < 0) {
i_errno = IECTRLREAD;
return -1;
}
errno = ntohl(err);
return -1;
default:
i_errno = IEMESSAGE;
return -1;
}
return 0;
}
/* iperf_connect -- client to server connection function */
int
iperf_connect(struct iperf_test *test)
{
int opt;
socklen_t len;
if (NULL == test)
{
iperf_err(NULL, "No test\n");
return -1;
}
FD_ZERO(&test->read_set);
FD_ZERO(&test->write_set);
make_cookie(test->cookie);
/* Create and connect the control channel */
if (test->ctrl_sck < 0)
// Create the control channel using an ephemeral port
test->ctrl_sck = netdial(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, 0, test->server_hostname, test->server_port, test->settings->connect_timeout);
if (test->ctrl_sck < 0) {
i_errno = IECONNECT;
return -1;
}
// set TCP_NODELAY for lower latency on control messages
int flag = 1;
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
i_errno = IESETNODELAY;
return -1;
}
#if defined (HAVE_TCP_KEEPALIVE)
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
if (iperf_set_control_keepalive(test) < 0)
return -1;
#endif //HAVE_TCP_KEEPALIVE
#if defined(HAVE_TCP_USER_TIMEOUT)
if ((opt = test->settings->snd_timeout)) {
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
i_errno = IESETUSERTIMEOUT;
return -1;
}
}
#endif /* HAVE_TCP_USER_TIMEOUT */
if (Nwrite(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) < 0) {
i_errno = IESENDCOOKIE;
return -1;
}
FD_SET(test->ctrl_sck, &test->read_set);
if (test->ctrl_sck > test->max_fd) test->max_fd = test->ctrl_sck;
len = sizeof(opt);
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_MAXSEG, &opt, &len) < 0) {
test->ctrl_sck_mss = 0;
}
else {
if (opt > 0 && opt <= MAX_UDP_BLOCKSIZE) {
test->ctrl_sck_mss = opt;
}
else {
char str[WARN_STR_LEN];
snprintf(str, sizeof(str),
"Ignoring nonsense TCP MSS %d", opt);
warning(str);
test->ctrl_sck_mss = 0;
}
}
if (test->verbose) {
printf("Control connection MSS %d\n", test->ctrl_sck_mss);
}
/*
* If we're doing a UDP test and the block size wasn't explicitly
* set, then use the known MSS of the control connection to pick
* an appropriate default. If we weren't able to get the
* MSS for some reason, then default to something that should
* work on non-jumbo-frame Ethernet networks. The goal is to
* pick a reasonable default that is large but should get from
* sender to receiver without any IP fragmentation.
*
* We assume that the control connection is routed the same as the
* data packets (thus has the same PMTU). Also in the case of
* --reverse tests, we assume that the MTU is the same in both
* directions. Note that even if the algorithm guesses wrong,
* the user always has the option to override.
*/
if (test->protocol->id == Pudp) {
if (test->settings->blksize == 0) {
if (test->ctrl_sck_mss) {
test->settings->blksize = test->ctrl_sck_mss;
}
else {
test->settings->blksize = DEFAULT_UDP_BLKSIZE;
}
if (test->verbose) {
printf("Setting UDP block size to %d\n", test->settings->blksize);
}
}
/*
* Regardless of whether explicitly or implicitly set, if the
* block size is larger than the MSS, print a warning.
*/
if (test->ctrl_sck_mss > 0 &&
test->settings->blksize > test->ctrl_sck_mss) {
char str[WARN_STR_LEN];
snprintf(str, sizeof(str),
"UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss);
warning(str);
}
}
return 0;
}
int
iperf_client_end(struct iperf_test *test)
{
if (NULL == test)
{
iperf_err(NULL, "No test\n");
return -1;
}
struct iperf_stream *sp;
/* Close all stream sockets */
SLIST_FOREACH(sp, &test->streams, streams) {
close(sp->socket);
}
/* show final summary */
test->reporter_callback(test);
/* Send response only if no error in server */
if (test->state > 0) {
if (iperf_set_send_state(test, IPERF_DONE) != 0)
return -1;
}
/* Close control socket */
if (test->ctrl_sck >= 0)
close(test->ctrl_sck);
return 0;
}
int
iperf_run_client(struct iperf_test * test)
{
int startup;
int result = 0;
fd_set read_set, write_set;
struct iperf_time now;
struct timeval* timeout = NULL;
struct iperf_stream *sp;
struct iperf_time last_receive_time;
struct iperf_time diff_time;
struct timeval used_timeout;
iperf_size_t last_receive_blocks;
int64_t t_usecs;
int64_t timeout_us;
int64_t rcv_timeout_us;
int i_errno_save;
if (NULL == test)
{
iperf_err(NULL, "No test\n");
return -1;
}
if (test->logfile)
if (iperf_open_logfile(test) < 0)
return -1;
if (test->affinity != -1)
if (iperf_setaffinity(test, test->affinity) != 0)
return -1;
if (test->json_output)
if (iperf_json_start(test) < 0)
return -1;
if (test->json_output) {
cJSON_AddItemToObject(test->json_start, "version", cJSON_CreateString(version));
cJSON_AddItemToObject(test->json_start, "system_info", cJSON_CreateString(get_system_info()));
} else if (test->verbose) {
iperf_printf(test, "%s\n", version);
iperf_printf(test, "%s", "");
iperf_printf(test, "%s\n", get_system_info());
iflush(test);
}
/* Start the client and connect to the server */
if (iperf_connect(test) < 0)
goto cleanup_and_fail;
/* Begin calculating CPU utilization */
cpu_util(NULL);
if (test->mode != SENDER)
rcv_timeout_us = (test->settings->rcv_timeout.secs * SEC_TO_US) + test->settings->rcv_timeout.usecs;
else
rcv_timeout_us = 0;
iperf_time_now(&last_receive_time); // Initialize last time something was received
last_receive_blocks = 0;
startup = 1;
while (test->state != IPERF_DONE) {
memcpy(&read_set, &test->read_set, sizeof(fd_set));
memcpy(&write_set, &test->write_set, sizeof(fd_set));
iperf_time_now(&now);
timeout = tmr_timeout(&now);
// In reverse active mode client ensures data is received
if (test->state == TEST_RUNNING && rcv_timeout_us > 0) {
timeout_us = -1;
if (timeout != NULL) {
used_timeout.tv_sec = timeout->tv_sec;
used_timeout.tv_usec = timeout->tv_usec;
timeout_us = (timeout->tv_sec * SEC_TO_US) + timeout->tv_usec;
}
/* Cap the maximum select timeout at 1 second */
if (timeout_us > SEC_TO_US) {
timeout_us = SEC_TO_US;
}
if (timeout_us < 0 || timeout_us > rcv_timeout_us) {
used_timeout.tv_sec = test->settings->rcv_timeout.secs;
used_timeout.tv_usec = test->settings->rcv_timeout.usecs;
}
timeout = &used_timeout;
}
#if (defined(__vxworks)) || (defined(__VXWORKS__))
if (timeout != NULL && timeout->tv_sec == 0 && timeout->tv_usec == 0) {
taskDelay (1);
}
result = select(test->max_fd + 1,
&read_set,
(test->state == TEST_RUNNING && !test->reverse) ? &write_set : NULL,
NULL,
timeout);
#else
result = select(test->max_fd + 1, &read_set, &write_set, NULL, timeout);
#endif // __vxworks or __VXWORKS__
if (result < 0 && errno != EINTR) {
i_errno = IESELECT;
goto cleanup_and_fail;
} else if (result == 0 && test->state == TEST_RUNNING && rcv_timeout_us > 0) {
/*
* If nothing was received in non-reverse running state
* then probably something got stuck - either client,
* server or network, and test should be terminated./
*/
iperf_time_now(&now);
if (iperf_time_diff(&now, &last_receive_time, &diff_time) == 0) {
t_usecs = iperf_time_in_usecs(&diff_time);
if (t_usecs > rcv_timeout_us) {
/* Idle timeout if no new blocks received */
if (test->blocks_received == last_receive_blocks) {
i_errno = IENOMSG;
goto cleanup_and_fail;
}
}
}
}
/* See if the test is making progress */
if (test->blocks_received > last_receive_blocks) {
last_receive_blocks = test->blocks_received;
last_receive_time = now;
}
if (result > 0) {
if (FD_ISSET(test->ctrl_sck, &read_set)) {
if (iperf_handle_message_client(test) < 0) {
goto cleanup_and_fail;
}
FD_CLR(test->ctrl_sck, &read_set);
}
}
if (test->state == TEST_RUNNING) {
/* Is this our first time really running? */
if (startup) {
startup = 0;
/* Create and spin up threads */
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) {
i_errno = IEPTHREADATTRINIT;
goto cleanup_and_fail;
}
SLIST_FOREACH(sp, &test->streams, streams) {
if (pthread_create(&(sp->thr), &attr, &iperf_client_worker_run, sp) != 0) {
i_errno = IEPTHREADCREATE;
goto cleanup_and_fail;
}
sp->thread_created = 1;
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d created\n", sp->socket);
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "All threads created\n");
}
if (pthread_attr_destroy(&attr) != 0) {
i_errno = IEPTHREADATTRDESTROY;
goto cleanup_and_fail;
}
}
/* Run the timers. */
iperf_time_now(&now);
tmr_run(&now);
/*
* Is the test done yet? We have to be out of omitting
* mode, and then we have to have fulfilled one of the
* ending criteria, either by times, bytes, or blocks.
* The bytes and blocks tests needs to handle both the
* cases of the client being the sender and the client
* being the receiver.
*/
if ((!test->omitting) &&
(test->done ||
(test->settings->bytes != 0 && (test->bytes_sent >= test->settings->bytes ||
test->bytes_received >= test->settings->bytes)) ||
(test->settings->blocks != 0 && (test->blocks_sent >= test->settings->blocks ||
test->blocks_received >= test->settings->blocks)))) {
/* Cancel outstanding sender threads */
SLIST_FOREACH(sp, &test->streams, streams) {
if (sp->sender) {
int rc;
sp->done = 1;
if (sp->thread_created == 1) {
rc = pthread_cancel(sp->thr);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADCANCEL;
errno = rc;
iperf_err(test, "sender cancel in pthread_cancel - %s", iperf_strerror(i_errno));
goto cleanup_and_fail;
}
rc = pthread_join(sp->thr, NULL);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADJOIN;
errno = rc;
iperf_err(test, "sender cancel in pthread_join - %s", iperf_strerror(i_errno));
goto cleanup_and_fail;
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d stopped\n", sp->socket);
}
sp->thread_created = 0;
}
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Sender threads stopped\n");
}
/* Yes, done! Send TEST_END. */
test->done = 1;
cpu_util(test->cpu_util);
test->stats_callback(test);
if (iperf_set_send_state(test, TEST_END) != 0)
goto cleanup_and_fail;
}
}
}
/* Cancel outstanding receiver threads */
SLIST_FOREACH(sp, &test->streams, streams) {
if (!sp->sender) {
int rc;
sp->done = 1;
if (sp->thread_created == 1) {
rc = pthread_cancel(sp->thr);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADCANCEL;
errno = rc;
iperf_err(test, "receiver cancel in pthread_cancel - %s", iperf_strerror(i_errno));
goto cleanup_and_fail;
}
rc = pthread_join(sp->thr, NULL);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADJOIN;
errno = rc;
iperf_err(test, "receiver cancel in pthread_join - %s", iperf_strerror(i_errno));
goto cleanup_and_fail;
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d stopped\n", sp->socket);
}
sp->thread_created = 0;
}
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Receiver threads stopped\n");
}
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
} else {
iperf_printf(test, "\n");
iperf_printf(test, "%s", report_done);
}
iflush(test);
return 0;
cleanup_and_fail:
/* Cancel all outstanding threads */
i_errno_save = i_errno;
SLIST_FOREACH(sp, &test->streams, streams) {
if (sp->done) {
continue;
}
sp->done = 1;
int rc;
if (sp->thread_created == 1) {
rc = pthread_cancel(sp->thr);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADCANCEL;
errno = rc;
iperf_err(test, "cleanup_and_fail in pthread_cancel - %s", iperf_strerror(i_errno));
}
rc = pthread_join(sp->thr, NULL);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADJOIN;
errno = rc;
iperf_err(test, "cleanup_and_fail in pthread_join - %s", iperf_strerror(i_errno));
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d stopped\n", sp->socket);
}
sp->thread_created = 0;
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "All threads stopped\n");
}
i_errno = i_errno_save;
iperf_client_end(test);
if (test->json_output) {
cJSON_AddStringToObject(test->json_top, "error", iperf_strerror(i_errno));
iperf_json_finish(test);
}
iflush(test);
return -1;
}
@@ -0,0 +1,184 @@
/* src/iperf_config.h.in. Generated for Android JNI build. */
/* Can bind to device via SO_BINDTODEVICE or IP_BOUND_IF. */
/* #undef CAN_BIND_TO_DEVICE */
/* Define to 1 if you have the 'clock_gettime' function. */
#define HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the 'clock_nanosleep' function. */
/* #undef HAVE_CLOCK_NANOSLEEP */
/* Define to 1 if you have the 'cpuset_setaffinity' function. */
/* #undef HAVE_CPUSET_SETAFFINITY */
/* Have CPU affinity support. */
#define HAVE_CPU_AFFINITY 1
/* Define to 1 if you have the 'daemon' function. */
/* #undef HAVE_DAEMON */
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Have IP_MTU_DISCOVER/IP_DONTFRAG/IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_DONT_FRAGMENT */
/* Define to 1 if you have the <endian.h> header file. */
#define HAVE_ENDIAN_H 1
/* Have IPv6 flowlabel support. */
/* #undef HAVE_FLOWLABEL */
/* Define to 1 if you have the 'getline' function. */
#define HAVE_GETLINE 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Have MPTCP protocol. */
/* #undef HAVE_IPPROTO_MPTCP */
/* Have IP_BOUND_IF sockopt. */
/* #undef HAVE_IP_BOUND_IF */
/* Have IP_DONTFRAG sockopt. */
/* #undef HAVE_IP_DONTFRAG */
/* Have IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_IP_DONTFRAGMENT */
/* Have IP_MTU_DISCOVER sockopt. */
/* #undef HAVE_IP_MTU_DISCOVER */
/* Define to 1 if you have the <linux/tcp.h> header file. */
/* #undef HAVE_LINUX_TCP_H */
/* Have MSG_TRUNC recv option. */
#define HAVE_MSG_TRUNC 1
/* Define to 1 if you have the 'nanosleep' function. */
#define HAVE_NANOSLEEP 1
/* Define to 1 if you have the <netinet/sctp.h> header file. */
/* #undef HAVE_NETINET_SCTP_H */
/* Define to 1 if you have the <poll.h> header file. */
#define HAVE_POLL_H 1
/* Define if you have POSIX threads libraries and header files. */
#define HAVE_PTHREAD 1
/* Have PTHREAD_PRIO_INHERIT. */
/* #undef HAVE_PTHREAD_PRIO_INHERIT */
/* Define to 1 if you have the 'sched_setaffinity' function. */
/* #undef HAVE_SCHED_SETAFFINITY */
/* Have SCTP support. */
/* #undef HAVE_SCTP_H */
/* Define to 1 if you have the 'sendfile' function. */
/* #undef HAVE_SENDFILE */
/* Define to 1 if you have the 'SetProcessAffinityMask' function. */
/* #undef HAVE_SETPROCESSAFFINITYMASK */
/* Have SHUT_WR socket shutdown option. */
#define HAVE_SOCKET_SHUTDOWN_SHUT_WR 1
/* Have SO_BINDTODEVICE sockopt. */
/* #undef HAVE_SO_BINDTODEVICE */
/* Have SO_MAX_PACING_RATE sockopt. */
/* #undef HAVE_SO_MAX_PACING_RATE */
/* OpenSSL Is Available */
/* #undef HAVE_SSL */
/* Define to 1 if you have the <stdatomic.h> header file. */
#define HAVE_STDATOMIC_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdio.h> header file. */
#define HAVE_STDIO_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if the system has the type 'struct sctp_assoc_value'. */
/* #undef HAVE_STRUCT_SCTP_ASSOC_VALUE */
/* Define to 1 if you have the <sys/endian.h> header file. */
/* #undef HAVE_SYS_ENDIAN_H */
/* Define to 1 if you have the <sys/socket.h> header file. */
#define HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Have TCP_CONGESTION sockopt. */
#define HAVE_TCP_CONGESTION 1
/* Have TCP_KEEPIDLE sockopt. */
#define HAVE_TCP_KEEPALIVE 1
/* Have TCP_USER_TIMEOUT sockopt. */
#define HAVE_TCP_USER_TIMEOUT 1
/* Have UDP_GRO sockopt. */
/* #undef HAVE_UDP_GRO */
/* Have UDP_SEGMENT sockopt. */
/* #undef HAVE_UDP_SEGMENT */
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#define LT_OBJDIR ".libs/"
/* Name of package */
#define PACKAGE "iperf"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "iperf-devel@lists.es.net"
/* Define to the full name of this package. */
#define PACKAGE_NAME "iperf"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "iperf 3.19"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "iperf"
/* Define to the home page for this package. */
#define PACKAGE_URL "http://iperf.fr/"
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.19"
/* Define to necessary symbol if this constant uses a non-standard name on your system. */
#define PTHREAD_CREATE_JOINABLE 2
/* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */
#define STDC_HEADERS 1
/* Version number of package */
#define VERSION "3.19"
/* Define to empty if 'const' does not conform to ANSI C. */
/* #undef const */
@@ -0,0 +1,175 @@
/* src/iperf_config.h.in. Generated from configure.ac by autoheader. */
/* Define to 1 if you have the 'clock_gettime' function. */
#undef HAVE_CLOCK_GETTIME
/* Define to 1 if you have the 'clock_nanosleep' function. */
#undef HAVE_CLOCK_NANOSLEEP
/* Define to 1 if you have the 'cpuset_setaffinity' function. */
#undef HAVE_CPUSET_SETAFFINITY
/* Have CPU affinity support. */
#undef HAVE_CPU_AFFINITY
/* Define to 1 if you have the 'daemon' function. */
#undef HAVE_DAEMON
/* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H
/* Have IP_MTU_DISCOVER/IP_DONTFRAG/IP_DONTFRAGMENT sockopt. */
#undef HAVE_DONT_FRAGMENT
/* Define to 1 if you have the <endian.h> header file. */
#undef HAVE_ENDIAN_H
/* Have IPv6 flowlabel support. */
#undef HAVE_FLOWLABEL
/* Define to 1 if you have the 'getline' function. */
#undef HAVE_GETLINE
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Have MPTCP protocol. */
#undef HAVE_IPPROTO_MPTCP
/* Have IP_DONTFRAG sockopt. */
#undef HAVE_IP_DONTFRAG
/* Have IP_DONTFRAGMENT sockopt. */
#undef HAVE_IP_DONTFRAGMENT
/* Have IP_MTU_DISCOVER sockopt. */
#undef HAVE_IP_MTU_DISCOVER
/* Define to 1 if you have the <linux/tcp.h> header file. */
#undef HAVE_LINUX_TCP_H
/* Have MSG_TRUNC recv option. */
#undef HAVE_MSG_TRUNC
/* Define to 1 if you have the 'nanosleep' function. */
#undef HAVE_NANOSLEEP
/* Define to 1 if you have the <netinet/sctp.h> header file. */
#undef HAVE_NETINET_SCTP_H
/* Define to 1 if you have the <poll.h> header file. */
#undef HAVE_POLL_H
/* Define if you have POSIX threads libraries and header files. */
#undef HAVE_PTHREAD
/* Have PTHREAD_PRIO_INHERIT. */
#undef HAVE_PTHREAD_PRIO_INHERIT
/* Define to 1 if you have the 'sched_setaffinity' function. */
#undef HAVE_SCHED_SETAFFINITY
/* Have SCTP support. */
#undef HAVE_SCTP_H
/* Define to 1 if you have the 'sendfile' function. */
#undef HAVE_SENDFILE
/* Define to 1 if you have the 'SetProcessAffinityMask' function. */
#undef HAVE_SETPROCESSAFFINITYMASK
/* Have SO_BINDTODEVICE sockopt. */
#undef HAVE_SO_BINDTODEVICE
/* Have SO_MAX_PACING_RATE sockopt. */
#undef HAVE_SO_MAX_PACING_RATE
/* OpenSSL Is Available */
#undef HAVE_SSL
/* Define to 1 if you have the <stdatomic.h> header file. */
#undef HAVE_STDATOMIC_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdio.h> header file. */
#undef HAVE_STDIO_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if the system has the type 'struct sctp_assoc_value'. */
#undef HAVE_STRUCT_SCTP_ASSOC_VALUE
/* Define to 1 if you have the <sys/endian.h> header file. */
#undef HAVE_SYS_ENDIAN_H
/* Define to 1 if you have the <sys/socket.h> header file. */
#undef HAVE_SYS_SOCKET_H
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Have TCP_CONGESTION sockopt. */
#undef HAVE_TCP_CONGESTION
/* Have tcpi_snd_wnd field in tcp_info. */
#undef HAVE_TCP_INFO_SND_WND
/* Have TCP_KEEPIDLE sockopt. */
#undef HAVE_TCP_KEEPALIVE
/* Have TCP_USER_TIMEOUT sockopt. */
#undef HAVE_TCP_USER_TIMEOUT
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#undef LT_OBJDIR
/* Name of package */
#undef PACKAGE
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define to necessary symbol if this constant uses a non-standard name on
your system. */
#undef PTHREAD_CREATE_JOINABLE
/* Define to 1 if all of the C89 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#undef STDC_HEADERS
/* Version number of package */
#undef VERSION
/* Define to empty if 'const' does not conform to ANSI C. */
#undef const
@@ -0,0 +1,184 @@
/* src/iperf_config.h.in. Generated for Android JNI build. */
/* Can bind to device via SO_BINDTODEVICE or IP_BOUND_IF. */
/* #undef CAN_BIND_TO_DEVICE */
/* Define to 1 if you have the 'clock_gettime' function. */
#define HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the 'clock_nanosleep' function. */
/* #undef HAVE_CLOCK_NANOSLEEP */
/* Define to 1 if you have the 'cpuset_setaffinity' function. */
/* #undef HAVE_CPUSET_SETAFFINITY */
/* Have CPU affinity support. */
#define HAVE_CPU_AFFINITY 1
/* Define to 1 if you have the 'daemon' function. */
/* #undef HAVE_DAEMON */
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Have IP_MTU_DISCOVER/IP_DONTFRAG/IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_DONT_FRAGMENT */
/* Define to 1 if you have the <endian.h> header file. */
#define HAVE_ENDIAN_H 1
/* Have IPv6 flowlabel support. */
/* #undef HAVE_FLOWLABEL */
/* Define to 1 if you have the 'getline' function. */
#define HAVE_GETLINE 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Have MPTCP protocol. */
/* #undef HAVE_IPPROTO_MPTCP */
/* Have IP_BOUND_IF sockopt. */
/* #undef HAVE_IP_BOUND_IF */
/* Have IP_DONTFRAG sockopt. */
/* #undef HAVE_IP_DONTFRAG */
/* Have IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_IP_DONTFRAGMENT */
/* Have IP_MTU_DISCOVER sockopt. */
/* #undef HAVE_IP_MTU_DISCOVER */
/* Define to 1 if you have the <linux/tcp.h> header file. */
/* #undef HAVE_LINUX_TCP_H */
/* Have MSG_TRUNC recv option. */
#define HAVE_MSG_TRUNC 1
/* Define to 1 if you have the 'nanosleep' function. */
#define HAVE_NANOSLEEP 1
/* Define to 1 if you have the <netinet/sctp.h> header file. */
/* #undef HAVE_NETINET_SCTP_H */
/* Define to 1 if you have the <poll.h> header file. */
#define HAVE_POLL_H 1
/* Define if you have POSIX threads libraries and header files. */
#define HAVE_PTHREAD 1
/* Have PTHREAD_PRIO_INHERIT. */
/* #undef HAVE_PTHREAD_PRIO_INHERIT */
/* Define to 1 if you have the 'sched_setaffinity' function. */
/* #undef HAVE_SCHED_SETAFFINITY */
/* Have SCTP support. */
/* #undef HAVE_SCTP_H */
/* Define to 1 if you have the 'sendfile' function. */
/* #undef HAVE_SENDFILE */
/* Define to 1 if you have the 'SetProcessAffinityMask' function. */
/* #undef HAVE_SETPROCESSAFFINITYMASK */
/* Have SHUT_WR socket shutdown option. */
#define HAVE_SOCKET_SHUTDOWN_SHUT_WR 1
/* Have SO_BINDTODEVICE sockopt. */
/* #undef HAVE_SO_BINDTODEVICE */
/* Have SO_MAX_PACING_RATE sockopt. */
/* #undef HAVE_SO_MAX_PACING_RATE */
/* OpenSSL Is Available */
/* #undef HAVE_SSL */
/* Define to 1 if you have the <stdatomic.h> header file. */
#define HAVE_STDATOMIC_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdio.h> header file. */
#define HAVE_STDIO_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if the system has the type 'struct sctp_assoc_value'. */
/* #undef HAVE_STRUCT_SCTP_ASSOC_VALUE */
/* Define to 1 if you have the <sys/endian.h> header file. */
/* #undef HAVE_SYS_ENDIAN_H */
/* Define to 1 if you have the <sys/socket.h> header file. */
#define HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Have TCP_CONGESTION sockopt. */
#define HAVE_TCP_CONGESTION 1
/* Have TCP_KEEPIDLE sockopt. */
#define HAVE_TCP_KEEPALIVE 1
/* Have TCP_USER_TIMEOUT sockopt. */
#define HAVE_TCP_USER_TIMEOUT 1
/* Have UDP_GRO sockopt. */
/* #undef HAVE_UDP_GRO */
/* Have UDP_SEGMENT sockopt. */
/* #undef HAVE_UDP_SEGMENT */
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#define LT_OBJDIR ".libs/"
/* Name of package */
#define PACKAGE "iperf"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "iperf-devel@lists.es.net"
/* Define to the full name of this package. */
#define PACKAGE_NAME "iperf"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "iperf 3.19"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "iperf"
/* Define to the home page for this package. */
#define PACKAGE_URL "http://iperf.fr/"
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.19"
/* Define to necessary symbol if this constant uses a non-standard name on your system. */
#define PTHREAD_CREATE_JOINABLE 2
/* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */
#define STDC_HEADERS 1
/* Version number of package */
#define VERSION "3.19"
/* Define to empty if 'const' does not conform to ANSI C. */
/* #undef const */
@@ -0,0 +1,568 @@
/*
* iperf, Copyright (c) 2014-2022, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "iperf.h"
#include "iperf_api.h"
int gerror;
char iperf_timestrerr[100];
/* Do a printf to stderr. */
void
iperf_err(struct iperf_test *test, const char *format, ...)
{
va_list argp;
char str[1000];
time_t now;
struct tm *ltm = NULL;
char *ct = NULL;
/* Timestamp if requested */
if (test != NULL && test->timestamps) {
time(&now);
ltm = localtime(&now);
strftime(iperf_timestrerr, sizeof(iperf_timestrerr), test->timestamp_format, ltm);
ct = iperf_timestrerr;
}
va_start(argp, format);
vsnprintf(str, sizeof(str), format, argp);
if (test != NULL && test->json_output && test->json_top != NULL)
cJSON_AddStringToObject(test->json_top, "error", str);
else {
if (test != NULL && pthread_mutex_lock(&(test->print_mutex)) != 0) {
perror("iperf_err: pthread_mutex_lock");
}
if (test != NULL && test->outfile != NULL && test->outfile != stdout) {
if (ct) {
fprintf(test->outfile, "%s", ct);
}
fprintf(test->outfile, "iperf3: %s\n", str);
}
else {
if (ct) {
fprintf(stderr, "%s", ct);
}
fprintf(stderr, "iperf3: %s\n", str);
}
if (test != NULL && pthread_mutex_unlock(&(test->print_mutex)) != 0) {
perror("iperf_err: pthread_mutex_unlock");
}
}
va_end(argp);
}
/* Do a printf to stderr or log file as appropriate, then exit(0). */
void
iperf_signormalexit(struct iperf_test *test, const char *format, ...)
{
va_list argp;
va_start(argp, format);
iperf_exit(test, 0, format, argp);
}
/* Do a printf to stderr or log file as appropriate, then exit(1). */
void
iperf_errexit(struct iperf_test *test, const char *format, ...)
{
va_list argp;
va_start(argp, format);
iperf_exit(test, 1, format, argp);
}
/* Do a printf to stderr or log file as appropriate, then exit. */
void
iperf_exit(struct iperf_test *test, int exit_code, const char *format, va_list argp)
{
char str[1000];
time_t now;
struct tm *ltm = NULL;
char *ct = NULL;
/* Timestamp if requested */
if (test != NULL && test->timestamps) {
time(&now);
ltm = localtime(&now);
strftime(iperf_timestrerr, sizeof(iperf_timestrerr), iperf_get_test_timestamp_format(test), ltm);
ct = iperf_timestrerr;
}
vsnprintf(str, sizeof(str), format, argp);
if (test != NULL && test->json_output) {
if (test->json_top != NULL) {
cJSON_AddStringToObject(test->json_top, "error", str);
}
iperf_json_finish(test);
} else {
if (test != NULL && pthread_mutex_lock(&(test->print_mutex)) != 0) {
perror("iperf_errexit: pthread_mutex_lock");
}
if (test && test->outfile && test->outfile != stdout) {
if (ct) {
fprintf(test->outfile, "%s", ct);
}
fprintf(test->outfile, "iperf3: %s\n", str);
}
else {
if (ct) {
fprintf(stderr, "%s", ct);
}
fprintf(stderr, "iperf3: %s\n", str);
}
if (test != NULL && pthread_mutex_unlock(&(test->print_mutex)) != 0) {
perror("iperf_errexit: pthread_mutex_unlock");
}
}
va_end(argp);
if (test)
iperf_delete_pidfile(test);
exit(exit_code);
}
int i_errno;
char *
iperf_strerror(int int_errno)
{
static char errstr[256];
int len, perr, herr;
perr = herr = 0;
len = sizeof(errstr);
memset(errstr, 0, len);
switch (int_errno) {
case IENONE:
snprintf(errstr, len, "no error");
break;
case IESERVCLIENT:
snprintf(errstr, len, "cannot be both server and client");
break;
case IENOROLE:
snprintf(errstr, len, "must either be a client (-c) or server (-s)");
break;
case IESERVERONLY:
snprintf(errstr, len, "some option you are trying to set is server only");
break;
case IECLIENTONLY:
snprintf(errstr, len, "some option you are trying to set is client only");
break;
case IEDURATION:
snprintf(errstr, len, "test duration valid values are 0 to %d seconds", MAX_TIME);
break;
case IENUMSTREAMS:
snprintf(errstr, len, "number of parallel streams too large (maximum = %d)", MAX_STREAMS);
break;
case IEBLOCKSIZE:
snprintf(errstr, len, "block size too large (maximum = %d bytes)", MAX_BLOCKSIZE);
break;
case IEBUFSIZE:
snprintf(errstr, len, "socket buffer size too large (maximum = %d bytes)", MAX_TCP_BUFFER);
break;
case IEINTERVAL:
snprintf(errstr, len, "invalid report interval (min = %g, max = %g seconds)", MIN_INTERVAL, MAX_INTERVAL);
break;
case IEBIND: /* UNUSED */
snprintf(errstr, len, "--bind must be specified to use --cport");
break;
case IEUDPBLOCKSIZE:
snprintf(errstr, len, "block size invalid (minimum = %d bytes, maximum = %d bytes)", MIN_UDP_BLOCKSIZE, MAX_UDP_BLOCKSIZE);
break;
case IEBADTOS:
snprintf(errstr, len, "bad TOS value (must be between 0 and 255 inclusive)");
break;
case IESETCLIENTAUTH:
snprintf(errstr, len, "you must specify a username, password, and path to a valid RSA public key");
break;
case IESETSERVERAUTH:
snprintf(errstr, len, "you must specify a path to a valid RSA private key and a user credential file");
break;
case IESERVERAUTHUSERS:
snprintf(errstr, len, "cannot access authorized users file");
break;
case IEBADFORMAT:
snprintf(errstr, len, "bad format specifier (valid formats are in the set [kmgtKMGT])");
break;
case IEBADPORT:
snprintf(errstr, len, "port number must be between 1 and 65535 inclusive");
break;
case IEMSS:
snprintf(errstr, len, "TCP MSS too large (maximum = %d bytes)", MAX_MSS);
break;
case IENOSENDFILE:
snprintf(errstr, len, "this OS does not support sendfile");
break;
case IEOMIT:
snprintf(errstr, len, "bogus value for --omit (maximum = %d seconds)", MAX_OMIT_TIME);
break;
case IEUNIMP:
snprintf(errstr, len, "an option you are trying to set is not implemented yet");
break;
case IEFILE:
snprintf(errstr, len, "unable to open -F file");
perr = 1;
break;
case IEBURST:
snprintf(errstr, len, "invalid burst count (maximum = %d)", MAX_BURST);
break;
case IEENDCONDITIONS:
snprintf(errstr, len, "only one test end condition (-t, -n, -k) may be specified");
break;
case IELOGFILE:
snprintf(errstr, len, "unable to open log file");
perr = 1;
break;
case IENOSCTP:
snprintf(errstr, len, "no SCTP support available");
break;
case IENEWTEST:
snprintf(errstr, len, "unable to create a new test");
perr = 1;
break;
case IEINITTEST:
snprintf(errstr, len, "test initialization failed");
perr = 1;
break;
case IEAUTHTEST:
snprintf(errstr, len, "test authorization failed");
break;
case IELISTEN:
snprintf(errstr, len, "unable to start listener for connections");
herr = 1;
perr = 1;
break;
case IECONNECT:
snprintf(errstr, len, "unable to connect to server - server may have stopped running or use a different port, firewall issue, etc.");
perr = 1;
herr = 1;
break;
case IEACCEPT:
snprintf(errstr, len, "unable to accept connection from client");
herr = 1;
perr = 1;
break;
case IESENDCOOKIE:
snprintf(errstr, len, "unable to send cookie to server");
perr = 1;
break;
case IERECVCOOKIE:
snprintf(errstr, len, "unable to receive cookie at server");
perr = 1;
break;
case IECTRLWRITE:
snprintf(errstr, len, "unable to write to the control socket");
perr = 1;
break;
case IECTRLREAD:
snprintf(errstr, len, "unable to read from the control socket");
perr = 1;
break;
case IECTRLCLOSE:
snprintf(errstr, len, "control socket has closed unexpectedly");
break;
case IEMESSAGE:
snprintf(errstr, len, "received an unknown control message (ensure other side is iperf3 and not iperf)");
break;
case IESENDMESSAGE:
snprintf(errstr, len, "unable to send control message - port may not be available, the other side may have stopped running, etc.");
perr = 1;
break;
case IERECVMESSAGE:
snprintf(errstr, len, "unable to receive control message - port may not be available, the other side may have stopped running, etc.");
perr = 1;
break;
case IESENDPARAMS:
snprintf(errstr, len, "unable to send parameters to server");
perr = 1;
break;
case IERECVPARAMS:
snprintf(errstr, len, "unable to receive parameters from client");
perr = 1;
break;
case IEPACKAGERESULTS:
snprintf(errstr, len, "unable to package results");
perr = 1;
break;
case IESENDRESULTS:
snprintf(errstr, len, "unable to send results");
perr = 1;
break;
case IERECVRESULTS:
snprintf(errstr, len, "unable to receive results");
perr = 1;
break;
case IESELECT:
snprintf(errstr, len, "select failed");
perr = 1;
break;
case IECLIENTTERM:
snprintf(errstr, len, "the client has terminated");
break;
case IESERVERTERM:
snprintf(errstr, len, "the server has terminated");
break;
case IEACCESSDENIED:
snprintf(errstr, len, "the server is busy running a test. try again later");
break;
case IESETNODELAY:
snprintf(errstr, len, "unable to set TCP/SCTP NODELAY");
perr = 1;
break;
case IESETMSS:
snprintf(errstr, len, "unable to set TCP/SCTP MSS");
perr = 1;
break;
case IESETBUF:
snprintf(errstr, len, "unable to set socket buffer size");
perr = 1;
break;
case IESETTOS:
snprintf(errstr, len, "unable to set IP TOS");
perr = 1;
break;
case IESETCOS:
snprintf(errstr, len, "unable to set IPv6 traffic class");
perr = 1;
break;
case IESETFLOW:
snprintf(errstr, len, "unable to set IPv6 flow label");
break;
case IEREUSEADDR:
snprintf(errstr, len, "unable to reuse address on socket");
perr = 1;
break;
case IENONBLOCKING:
snprintf(errstr, len, "unable to set socket to non-blocking");
perr = 1;
break;
case IESETWINDOWSIZE:
snprintf(errstr, len, "unable to set socket window size");
perr = 1;
break;
case IEPROTOCOL:
snprintf(errstr, len, "protocol does not exist");
break;
case IEAFFINITY:
snprintf(errstr, len, "unable to set CPU affinity");
perr = 1;
break;
case IERCVTIMEOUT:
snprintf(errstr, len, "receive timeout value is incorrect or not in range");
perr = 1;
break;
case IESNDTIMEOUT:
snprintf(errstr, len, "send timeout value is incorrect or not in range");
perr = 1;
break;
case IEUDPFILETRANSFER:
snprintf(errstr, len, "cannot transfer file using UDP");
break;
case IERVRSONLYRCVTIMEOUT:
snprintf(errstr, len, "client receive timeout is valid only in receiving mode");
perr = 1;
break;
case IEDAEMON:
snprintf(errstr, len, "unable to become a daemon");
perr = 1;
break;
case IECREATESTREAM:
snprintf(errstr, len, "unable to create a new stream");
herr = 1;
perr = 1;
break;
case IEINITSTREAM:
snprintf(errstr, len, "unable to initialize stream");
herr = 1;
perr = 1;
break;
case IESTREAMLISTEN:
snprintf(errstr, len, "unable to start stream listener");
herr = 1;
perr = 1;
break;
case IESTREAMCONNECT:
snprintf(errstr, len, "unable to connect stream");
herr = 1;
perr = 1;
break;
case IESTREAMACCEPT:
snprintf(errstr, len, "unable to accept stream connection");
perr = 1;
break;
case IESTREAMWRITE:
snprintf(errstr, len, "unable to write to stream socket");
perr = 1;
break;
case IESTREAMREAD:
snprintf(errstr, len, "unable to read from stream socket");
perr = 1;
break;
case IESTREAMCLOSE:
snprintf(errstr, len, "stream socket has closed unexpectedly");
break;
case IESTREAMID:
snprintf(errstr, len, "stream has an invalid id");
break;
case IENEWTIMER:
snprintf(errstr, len, "unable to create new timer");
perr = 1;
break;
case IEUPDATETIMER:
snprintf(errstr, len, "unable to update timer");
perr = 1;
break;
case IESETCONGESTION:
snprintf(errstr, len, "unable to set TCP_CONGESTION: "
"Supplied congestion control algorithm not supported on this host");
break;
case IEPIDFILE:
snprintf(errstr, len, "unable to write PID file");
perr = 1;
break;
case IEV6ONLY:
snprintf(errstr, len, "Unable to set/reset IPV6_V6ONLY");
perr = 1;
break;
case IESETSCTPDISABLEFRAG:
snprintf(errstr, len, "unable to set SCTP_DISABLE_FRAGMENTS");
perr = 1;
break;
case IESETSCTPNSTREAM:
snprintf(errstr, len, "unable to set SCTP_INIT num of SCTP streams\n");
perr = 1;
break;
case IESETPACING:
snprintf(errstr, len, "unable to set socket pacing");
perr = 1;
break;
case IESETBUF2:
snprintf(errstr, len, "socket buffer size not set correctly");
break;
case IEREVERSEBIDIR:
snprintf(errstr, len, "cannot be both reverse and bidirectional");
break;
case IETOTALRATE:
snprintf(errstr, len, "total required bandwidth is larger than server limit");
break;
case IESKEWTHRESHOLD:
snprintf(errstr, len, "skew threshold must be a positive number");
break;
case IEIDLETIMEOUT:
snprintf(errstr, len, "idle timeout parameter is not positive or larger than allowed limit");
break;
case IEBINDDEV:
snprintf(errstr, len, "Unable to bind-to-device (check perror, maybe permissions?)");
break;
case IEBINDDEVNOSUPPORT:
snprintf(errstr, len, "`<ip>%%<dev>` is not supported as system does not support bind to device");
break;
case IEHOSTDEV:
snprintf(errstr, len, "host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address");
break;
case IENOMSG:
snprintf(errstr, len, "idle timeout for receiving data");
break;
case IESETDONTFRAGMENT:
snprintf(errstr, len, "unable to set IP Do-Not-Fragment flag");
break;
case IESETUSERTIMEOUT:
snprintf(errstr, len, "unable to set TCP USER_TIMEOUT");
perr = 1;
break;
case IEPTHREADCREATE:
snprintf(errstr, len, "unable to create thread");
perr = 1;
break;
case IEPTHREADCANCEL:
snprintf(errstr, len, "unable to cancel thread");
perr = 1;
break;
case IEPTHREADJOIN:
snprintf(errstr, len, "unable to join thread");
perr = 1;
break;
case IEPTHREADATTRINIT:
snprintf(errstr, len, "unable to create thread attributes");
perr = 1;
break;
case IEPTHREADSIGMASK:
snprintf(errstr, len, "unable to change mask of blocked signals");
break;
case IEPTHREADATTRDESTROY:
snprintf(errstr, len, "unable to destroy thread attributes");
case IECNTLKA:
snprintf(errstr, len, "control connection Keepalive period should be larger than the full retry period (interval * count)");
perr = 1;
break;
case IESETCNTLKA:
snprintf(errstr, len, "unable to set socket keepalive (SO_KEEPALIVE) option");
perr = 1;
break;
case IESETCNTLKAKEEPIDLE:
snprintf(errstr, len, "unable to set socket keepalive TCP period (TCP_KEEPIDLE) option");
perr = 1;
break;
case IESETCNTLKAINTERVAL:
snprintf(errstr, len, "unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option");
perr = 1;
break;
case IESETCNTLKACOUNT:
snprintf(errstr, len, "unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option");
perr = 1;
break;
default:
snprintf(errstr, len, "int_errno=%d", int_errno);
perr = 1;
break;
}
/* Append the result of strerror() or gai_strerror() if appropriate */
if (herr || perr)
strncat(errstr, ": ", len - strlen(errstr) - 1);
if (errno && perr)
strncat(errstr, strerror(errno), len - strlen(errstr) - 1);
else if (herr && gerror) {
strncat(errstr, gai_strerror(gerror), len - strlen(errstr) - 1);
gerror = 0;
}
return errstr;
}
@@ -0,0 +1,574 @@
/*---------------------------------------------------------------
* iperf, Copyright (c) 2014-2022, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*
* Based on code that is:
*
* Copyright (c) 1999,2000,2001,2002,2003
* The Board of Trustees of the University of Illinois
* All Rights Reserved.
*---------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software (Iperf) and associated
* documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
*
* Redistributions of source code must retain the above
* copyright notice, this list of conditions and
* the following disclaimers.
*
*
* Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimers in the documentation and/or other materials
* provided with the distribution.
*
*
* Neither the names of the University of Illinois, NCSA,
* nor the names of its contributors may be used to endorse
* or promote products derived from this Software without
* specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ________________________________________________________________
* National Laboratory for Applied Network Research
* National Center for Supercomputing Applications
* University of Illinois at Urbana-Champaign
* http://www.ncsa.uiuc.edu
* ________________________________________________________________
*
* Locale.c
* by Ajay Tirumala <tirumala@ncsa.uiuc.edu>
* & Mark Gates <mgates@nlanr.net>
* -------------------------------------------------------------------
* Strings and other stuff that is locale specific.
* ------------------------------------------------------------------- */
#include "iperf_config.h"
#include "version.h"
#include <inttypes.h>
#ifdef __cplusplus
extern "C"
{
#endif
/* -------------------------------------------------------------------
* usage
* ------------------------------------------------------------------- */
const char usage_shortstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
"Try `iperf3 --help' for more information.\n";
const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
" iperf3 [-h|--help] [-v|--version]\n\n"
"Server or Client:\n"
" -p, --port # server port to listen on/connect to\n"
" -f, --format [kmgtKMGT] format to report: Kbits, Mbits, Gbits, Tbits\n"
" -i, --interval # seconds between periodic throughput reports\n"
" -I, --pidfile file write PID file\n"
" -F, --file name xmit/recv the specified file\n"
#if defined(HAVE_CPU_AFFINITY)
" -A, --affinity n[,m] set CPU affinity core number to n (the core the process will use)\n"
" (optional Client only m - the Server's core number for this test)\n"
#endif /* HAVE_CPU_AFFINITY */
#if defined(HAVE_SO_BINDTODEVICE)
" -B, --bind <host>[%%<dev>] bind to the interface associated with the address <host>\n"
" (optional <dev> equivalent to `--bind-dev <dev>`)\n"
" --bind-dev <dev> bind to the network interface with SO_BINDTODEVICE\n"
#else /* HAVE_SO_BINDTODEVICE */
" -B, --bind <host> bind to the interface associated with the address <host>\n"
#endif /* HAVE_SO_BINDTODEVICE */
" -V, --verbose more detailed output\n"
" -J, --json output in JSON format\n"
" --json-stream output in line-delimited JSON format\n"
" --logfile f send output to a log file\n"
" --forceflush force flushing output at every interval\n"
" --timestamps<=format> emit a timestamp at the start of each output line\n"
" (optional \"=\" and format string as per strftime(3))\n"
" --rcv-timeout # idle timeout for receiving data (default %d ms)\n"
#if defined(HAVE_TCP_USER_TIMEOUT)
" --snd-timeout # timeout for unacknowledged TCP data\n"
" (in ms, default is system settings)\n"
#endif /* HAVE_TCP_USER_TIMEOUT */
#if defined(HAVE_TCP_KEEPALIVE)
" --cntl-ka[=#/#/#] use control connection TCP keepalive - KEEPIDLE/KEEPINTV/KEEPCNT\n"
" each value is optional with system settings default\n"
#endif //HAVE_TCP_KEEPALIVE
#if defined(HAVE_IPPROTO_MPTCP)
" -m, --mptcp use MPTCP rather than plain TCP\n"
#endif
" -d, --debug[=#] emit debugging output\n"
" (optional optional \"=\" and debug level: 1-4. Default is 4 - all messages)\n"
" -v, --version show version information and quit\n"
" -h, --help show this message and quit\n"
"Server specific:\n"
" -s, --server run in server mode\n"
" -D, --daemon run the server as a daemon\n"
" -1, --one-off handle one client connection then exit\n"
" --server-bitrate-limit #[KMG][/#] server's total bit rate limit (default 0 = no limit)\n"
" (optional slash and number of secs interval for averaging\n"
" total data rate. Default is 5 seconds)\n"
" --idle-timeout # restart idle server after # seconds in case it\n"
" got stuck (default - no timeout)\n"
#if defined(HAVE_SSL)
" --rsa-private-key-path path to the RSA private key used to decrypt\n"
" authentication credentials\n"
" --authorized-users-path path to the configuration file containing user\n"
" credentials\n"
" --time-skew-threshold time skew threshold (in seconds) between the server\n"
" and client during the authentication process\n"
" --use-pkcs1-padding use pkcs1 padding at your own risk\n"
#endif //HAVE_SSL
"Client specific:\n"
" -c, --client <host>[%%<dev>] run in client mode, connecting to <host>\n"
" (option <dev> equivalent to `--bind-dev <dev>`)\n"
#if defined(HAVE_SCTP_H)
" --sctp use SCTP rather than TCP\n"
" -X, --xbind <name> bind SCTP association to links\n"
" --nstreams # number of SCTP streams\n"
#endif /* HAVE_SCTP_H */
" -u, --udp use UDP rather than TCP\n"
" --connect-timeout # timeout for control connection setup (ms)\n"
" -b, --bitrate #[KMG][/#] target bitrate in bits/sec (0 for unlimited)\n"
" (default %d Mbit/sec for UDP, unlimited for TCP)\n"
" (optional slash and packet count for burst mode)\n"
" --pacing-timer #[KMG] set the Server timing for pacing, in microseconds (default %d)\n"
" (deprecated - for servers using older versions ackward compatibility)\n"
#if defined(HAVE_SO_MAX_PACING_RATE)
" --fq-rate #[KMG] enable fair-queuing based socket pacing in\n"
" bits/sec (Linux only)\n"
#endif
" -t, --time # time in seconds to transmit for (default %d secs)\n"
" -n, --bytes #[KMG] transmit until the end of the interval when the client sent or received\n"
" (per direction) at least this number of bytes (instead of -t or -k)\n"
" -k, --blockcount #[KMG] transmit until the end of the interval when the client sent or received\n"
" (per direction) at least this number of blocks (instead of -t or -n)\n"
" -l, --length #[KMG] length of buffer to read or write\n"
" (default %d KB for TCP, dynamic or %d for UDP)\n"
" --cport <port> bind to a specific client port (TCP and UDP, default: ephemeral port)\n"
" -P, --parallel # number of parallel client streams to run\n"
" -R, --reverse run in reverse mode (server sends, client receives)\n"
" --bidir run in bidirectional mode.\n"
" Client and server send and receive data.\n"
" -w, --window #[KMG] set send/receive socket buffer sizes\n"
" (indirectly sets TCP window size)\n"
#if defined(HAVE_TCP_CONGESTION)
" -C, --congestion <algo> set TCP congestion control algorithm (Linux and FreeBSD only)\n"
#endif /* HAVE_TCP_CONGESTION */
" -M, --set-mss # set TCP/SCTP maximum segment size (MTU - 40 bytes)\n"
" -N, --no-delay set TCP/SCTP no delay, disabling Nagle's Algorithm\n"
" -4, --version4 only use IPv4\n"
" -6, --version6 only use IPv6\n"
" -S, --tos N set the IP type of service, 0-255.\n"
" The usual prefixes for octal and hex can be used,\n"
" i.e. 52, 064 and 0x34 all specify the same value.\n"
" --dscp N or --dscp val set the IP dscp value, either 0-63 or symbolic.\n"
" Numeric values can be specified in decimal,\n"
" octal and hex (see --tos above).\n"
#if defined(HAVE_FLOWLABEL)
" -L, --flowlabel N set the IPv6 flow label (only supported on Linux)\n"
#endif /* HAVE_FLOWLABEL */
" -Z, --zerocopy use a 'zero copy' method of sending data\n"
#if defined(HAVE_MSG_TRUNC)
" --skip-rx-copy ignore received messages using MSG_TRUNC option\n"
#endif /* HAVE_MSG_TRUNC */
" -O, --omit N perform pre-test for N seconds and omit the pre-test statistics\n"
" -T, --title str prefix every output line with this string\n"
" --extra-data str data string to include in client and server JSON\n"
" --get-server-output get results from server\n"
" --udp-counters-64bit use 64-bit counters in UDP test packets\n"
" --repeating-payload use repeating pattern in payload, instead of\n"
" randomized payload (like in iperf2)\n"
#if defined(HAVE_DONT_FRAGMENT)
" --dont-fragment set IPv4 Don't Fragment flag\n"
#endif /* HAVE_DONT_FRAGMENT */
#if defined(HAVE_SSL)
" --username username for authentication\n"
" --rsa-public-key-path path to the RSA public key used to encrypt\n"
" authentication credentials\n"
#endif //HAVESSL
#ifdef NOT_YET_SUPPORTED /* still working on these */
#endif
"\n"
"[KMG] indicates options that support a K/M/G suffix for kilo-, mega-, or giga-\n"
"\n"
#ifdef PACKAGE_URL
"iperf3 homepage at: " PACKAGE_URL "\n"
#endif /* PACKAGE_URL */
#ifdef PACKAGE_BUGREPORT
"Report bugs to: " PACKAGE_BUGREPORT "\n"
#endif /* PACKAGE_BUGREPORT */
;
#ifdef OBSOLETE /* from old iperf: no longer supported. Add some of these back someday */
"-d, --dualtest Do a bidirectional test simultaneously\n"
"-L, --listenport # port to receive bidirectional tests back on\n"
"-I, --stdin input the data to be transmitted from stdin\n"
"-F, --fileinput <name> input the data to be transmitted from a file\n"
"-r, --tradeoff Do a bidirectional test individually\n"
"-T, --ttl # time-to-live, for multicast (default 1)\n"
"-x, --reportexclude [CDMSV] exclude C(connection) D(data) M(multicast) S(settings) V(server) reports\n"
"-y, --reportstyle C report as a Comma-Separated Values\n"
#endif
const char version[] = PACKAGE_STRING;
/* -------------------------------------------------------------------
* settings
* ------------------------------------------------------------------- */
const char seperator_line[] =
"------------------------------------------------------------\n";
const char server_port[] =
"Server listening on %s port %d\n";
const char client_port[] =
"Client connecting to %s, %s port %d\n";
const char bind_address[] =
"Binding to local address %s\n";
const char bind_dev[] =
"Binding to local network device %s\n";
const char bind_port[] =
"Binding to local port %s\n";
const char multicast_ttl[] =
"Setting multicast TTL to %d\n";
const char join_multicast[] =
"Joining multicast group %s\n";
const char client_datagram_size[] =
"Sending %d byte datagrams\n";
const char server_datagram_size[] =
"Receiving %d byte datagrams\n";
const char tcp_window_size[] =
"TCP window size";
const char udp_buffer_size[] =
"UDP buffer size";
const char window_default[] =
"(default)";
const char wait_server_threads[] =
"Waiting for server threads to complete. Interrupt again to force quit.\n";
const char test_start_time[] =
"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %d second test, tos %d\n";
const char test_start_bytes[] =
"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %"PRIuFAST64" bytes to send, tos %d\n";
const char test_start_blocks[] =
"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %"PRIuFAST64" bytes to send, tos %d\n";
/* -------------------------------------------------------------------
* reports
* ------------------------------------------------------------------- */
const char report_time[] =
"Time: %s\n";
const char report_connecting[] =
"Connecting to host %s, port %d\n";
const char report_authentication_succeeded[] =
"Authentication succeeded for user '%s' ts %" PRIu64 "\n";
const char report_authentication_failed[] =
"Authentication failed with return code %d for user '%s' ts %" PRIu64 "\n";
const char report_reverse[] =
"Reverse mode, remote host %s is sending\n";
const char report_accepted[] =
"Accepted connection from %s, port %d\n";
const char report_cookie[] =
" Cookie: %s\n";
const char report_connected[] =
"[%3d] local %s port %d connected to %s port %d\n";
const char report_window[] =
"TCP window size: %s\n";
const char report_autotune[] =
"Using TCP Autotuning\n";
const char report_omit_done[] =
"Finished omit period, starting real test\n";
const char report_diskfile[] =
" Sent %s / %s (%d%%) of %s\n";
const char report_done[] =
"iperf Done.\n";
const char report_read_lengths[] =
"[%3d] Read lengths occurring in more than 5%% of reads:\n";
const char report_read_length_times[] =
"[%3d] %5d bytes read %5d times (%.3g%%)\n";
const char report_bw_header[] =
"[ ID] Interval Transfer Bitrate\n";
const char report_bw_header_bidir[] =
"[ ID][Role] Interval Transfer Bitrate\n";
const char report_bw_retrans_header[] =
"[ ID] Interval Transfer Bitrate Retr\n";
const char report_bw_retrans_header_bidir[] =
"[ ID][Role] Interval Transfer Bitrate Retr\n";
const char report_bw_retrans_cwnd_header[] =
"[ ID] Interval Transfer Bitrate Retr Cwnd\n";
const char report_bw_retrans_cwnd_header_bidir[] =
"[ ID][Role] Interval Transfer Bitrate Retr Cwnd\n";
const char report_bw_udp_header[] =
"[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams\n";
const char report_bw_udp_header_bidir[] =
"[ ID][Role] Interval Transfer Bitrate Jitter Lost/Total Datagrams\n";
const char report_bw_udp_sender_header[] =
"[ ID] Interval Transfer Bitrate Total Datagrams\n";
const char report_bw_udp_sender_header_bidir[] =
"[ ID][Role] Interval Transfer Bitrate Total Datagrams\n";
const char report_bw_format[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %s\n";
const char report_bw_retrans_format[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %3ld %s\n";
const char report_bw_retrans_cwnd_format[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %3ld %ss %s\n";
const char report_bw_udp_format[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %5.3f ms %" PRId64 "/%" PRId64 " (%.2g%%) %s\n";
const char report_bw_udp_format_no_omitted_error[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %5.3f ms Unknown/%" PRId64 " %s\n";
const char report_bw_udp_sender_format[] =
"[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %s %" PRId64 " %s\n";
const char report_summary[] =
"Test Complete. Summary Results:\n";
const char report_sum_bw_format[] =
"[SUM]%s %6.2f-%-6.2f sec %ss %ss/sec %s\n";
const char report_sum_bw_retrans_format[] =
"[SUM]%s %6.2f-%-6.2f sec %ss %ss/sec %3"PRId64" %s\n";
const char report_sum_bw_udp_format[] =
"[SUM]%s %6.2f-%-6.2f sec %ss %ss/sec %5.3f ms %" PRId64 "/%" PRId64 " (%.2g%%) %s\n";
const char report_sum_bw_udp_sender_format[] =
"[SUM]%s %6.2f-%-6.2f sec %ss %ss/sec %s %" PRId64 " %s\n";
const char report_omitted[] = "(omitted)";
const char report_bw_separator[] =
"- - - - - - - - - - - - - - - - - - - - - - - - -\n";
const char report_outoforder[] =
"[%3d]%s %4.1f-%4.1f sec %d datagrams received out-of-order\n";
const char report_sum_outoforder[] =
"[SUM]%s %4.1f-%4.1f sec %"PRIu64" datagrams received out-of-order\n";
const char report_peer[] =
"[%3d] local %s port %u connected with %s port %u\n";
const char report_mss_unsupported[] =
"[%3d] MSS and MTU size unknown (TCP_MAXSEG not supported by OS?)\n";
const char report_mss[] =
"[%3d] MSS size %d bytes (MTU %d bytes, %s)\n";
const char report_datagrams[] =
"[%3d] Sent %d datagrams\n";
const char report_sum_datagrams[] =
"[SUM] Sent %d datagrams\n";
const char server_reporting[] =
"[%3d] Server Report:\n";
const char reportCSV_peer[] =
"%s,%u,%s,%u";
const char report_cpu[] =
"CPU Utilization: %s/%s %.1f%% (%.1f%%u/%.1f%%s), %s/%s %.1f%% (%.1f%%u/%.1f%%s)\n";
const char report_local[] = "local";
const char report_remote[] = "remote";
const char report_sender[] = "sender";
const char report_receiver[] = "receiver";
const char report_sender_not_available_format[] = "[%3d] (sender statistics not available)\n";
const char report_sender_not_available_summary_format[] = "[%3s] (sender statistics not available)\n";
const char report_receiver_not_available_format[] = "[%3d] (receiver statistics not available)\n";
const char report_receiver_not_available_summary_format[] = "[%3s] (receiver statistics not available)\n";
#if defined(linux)
const char report_tcpInfo[] =
"event=TCP_Info CWND=%u SND_SSTHRESH=%u RCV_SSTHRESH=%u UNACKED=%u SACK=%u LOST=%u RETRANS=%u FACK=%u RTT=%u REORDERING=%u\n";
#endif
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
const char report_tcpInfo[] =
"event=TCP_Info CWND=%u RCV_WIND=%u SND_SSTHRESH=%u RTT=%u\n";
#endif
#ifdef HAVE_QUAD_SUPPORT
#ifdef HAVE_PRINTF_QD
const char reportCSV_bw_format[] =
"%s,%s,%d,%.1f-%.1f,%qd,%qd\n";
const char reportCSV_bw_udp_format[] =
"%s,%s,%d,%.1f-%.1f,%qd,%qd,%.3f,%d,%d,%.3f,%d\n";
#else // HAVE_PRINTF_QD
const char reportCSV_bw_format[] =
"%s,%s,%d,%.1f-%.1f,%lld,%lld\n";
const char reportCSV_bw_udp_format[] =
"%s,%s,%d,%.1f-%.1f,%lld,%lld,%.3f,%d,%d,%.3f,%d\n";
#endif // HAVE_PRINTF_QD
#else // HAVE_QUAD_SUPPORT
#ifdef WIN32
const char reportCSV_bw_format[] =
"%s,%s,%d,%.1f-%.1f,%I64d,%I64d\n";
const char reportCSV_bw_udp_format[] =
"%s,%s,%d,%.1f-%.1f,%I64d,%I64d,%.3f,%d,%d,%.3f,%d\n";
#else
const char reportCSV_bw_format[] =
"%s,%s,%d,%.1f-%.1f,%d,%d\n";
const char reportCSV_bw_udp_format[] =
"%s,%s,%d,%.1f-%.1f,%d,%d,%.3f,%d,%d,%.3f,%d\n";
#endif //WIN32
#endif //HAVE_QUAD_SUPPORT
/* -------------------------------------------------------------------
* warnings
* ------------------------------------------------------------------- */
const char warn_window_requested[] =
" (WARNING: requested %s)";
const char warn_window_small[] =
"WARNING: TCP window size set to %d bytes. A small window size\n\
will give poor performance. See the Iperf documentation.\n";
const char warn_delay_large[] =
"WARNING: delay too large, reducing from %.1f to 1.0 seconds.\n";
const char warn_no_pathmtu[] =
"WARNING: Path MTU Discovery may not be enabled.\n";
const char warn_no_ack[]=
"[%3d] WARNING: did not receive ack of last datagram after %d tries.\n";
const char warn_ack_failed[]=
"[%3d] WARNING: ack of last datagram failed after %d tries.\n";
const char warn_fileopen_failed[]=
"WARNING: Unable to open file stream for transfer\n\
Using default data stream. \n";
const char unable_to_change_win[]=
"WARNING: Unable to change the window size\n";
const char opt_estimate[]=
"Optimal Estimate\n";
const char report_interval_small[] =
"WARNING: interval too small, increasing from %3.2f to 0.5 seconds.\n";
const char warn_invalid_server_option[] =
"WARNING: option -%c is not valid for server mode\n";
const char warn_invalid_client_option[] =
"WARNING: option -%c is not valid for client mode\n";
const char warn_invalid_compatibility_option[] =
"WARNING: option -%c is not valid in compatibility mode\n";
const char warn_implied_udp[] =
"WARNING: option -%c implies udp testing\n";
const char warn_implied_compatibility[] =
"WARNING: option -%c has implied compatibility mode\n";
const char warn_buffer_too_small[] =
"WARNING: the UDP buffer was increased to %d for proper operation\n";
const char warn_invalid_single_threaded[] =
"WARNING: option -%c is not valid in single threaded versions\n";
const char warn_invalid_report_style[] =
"WARNING: unknown reporting style \"%s\", switching to default\n";
const char warn_invalid_report[] =
"WARNING: unknown reporting type \"%c\", ignored\n valid options are:\n\t exclude: C(connection) D(data) M(multicast) S(settings) V(server) report\n\n";
#ifdef __cplusplus
} /* end extern "C" */
#endif
@@ -0,0 +1,134 @@
/*
* iperf, Copyright (c) 2014-2020, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef IPERF_LOCALE_H
#define IPERF_LOCALE_H
extern const char usage_shortstr[];
extern const char usage_longstr[];
extern const char version[];
extern const char seperator_line[];
extern const char server_port[] ;
extern const char client_port[] ;
extern const char bind_address[] ;
extern const char bind_dev[] ;
extern const char multicast_ttl[] ;
extern const char join_multicast[] ;
extern const char client_datagram_size[] ;
extern const char server_datagram_size[] ;
extern const char tcp_window_size[] ;
extern const char udp_buffer_size[] ;
extern const char window_default[] ;
extern const char wait_server_threads[] ;
extern const char test_start_time[];
extern const char test_start_bytes[];
extern const char test_start_blocks[];
extern const char report_time[] ;
extern const char report_connecting[] ;
extern const char report_reverse[] ;
extern const char report_accepted[] ;
extern const char report_cookie[] ;
extern const char report_connected[] ;
extern const char report_authentication_succeeded[] ;
extern const char report_authentication_failed[] ;
extern const char report_window[] ;
extern const char report_autotune[] ;
extern const char report_omit_done[] ;
extern const char report_diskfile[] ;
extern const char report_done[] ;
extern const char report_read_lengths[] ;
extern const char report_read_length_times[] ;
extern const char report_bw_header[] ;
extern const char report_bw_header_bidir[] ;
extern const char report_bw_retrans_header[] ;
extern const char report_bw_retrans_header_bidir[] ;
extern const char report_bw_retrans_cwnd_header[] ;
extern const char report_bw_retrans_cwnd_header_bidir[] ;
extern const char report_bw_udp_header[] ;
extern const char report_bw_udp_header_bidir[] ;
extern const char report_bw_udp_sender_header[] ;
extern const char report_bw_udp_sender_header_bidir[] ;
extern const char report_bw_format[] ;
extern const char report_bw_retrans_format[] ;
extern const char report_bw_retrans_cwnd_format[] ;
extern const char report_bw_udp_format[] ;
extern const char report_bw_udp_format_no_omitted_error[] ;
extern const char report_bw_udp_sender_format[] ;
extern const char report_summary[] ;
extern const char report_sum_bw_format[] ;
extern const char report_sum_bw_retrans_format[] ;
extern const char report_sum_bw_udp_format[] ;
extern const char report_sum_bw_udp_sender_format[] ;
extern const char report_omitted[] ;
extern const char report_bw_separator[] ;
extern const char report_outoforder[] ;
extern const char report_sum_outoforder[] ;
extern const char report_peer[] ;
extern const char report_mss_unsupported[] ;
extern const char report_mss[] ;
extern const char report_datagrams[] ;
extern const char report_sum_datagrams[] ;
extern const char server_reporting[] ;
extern const char reportCSV_peer[] ;
extern const char report_cpu[] ;
extern const char report_local[] ;
extern const char report_remote[] ;
extern const char report_sender[] ;
extern const char report_receiver[] ;
extern const char report_sender_not_available_format[];
extern const char report_sender_not_available_summary_format[];
extern const char report_receiver_not_available_format[];
extern const char report_receiver_not_available_summary_format[];
extern const char report_tcpInfo[] ;
extern const char report_tcpInfo[] ;
extern const char warn_window_requested[] ;
extern const char warn_window_small[] ;
extern const char warn_delay_large[] ;
extern const char warn_no_pathmtu[] ;
extern const char warn_no_ack[];
extern const char warn_ack_failed[];
extern const char warn_fileopen_failed[];
extern const char unable_to_change_win[];
extern const char opt_estimate[];
extern const char report_interval_small[] ;
extern const char warn_invalid_server_option[] ;
extern const char warn_invalid_client_option[] ;
extern const char warn_invalid_compatibility_option[] ;
extern const char warn_implied_udp[] ;
extern const char warn_implied_compatibility[] ;
extern const char warn_buffer_too_small[] ;
extern const char warn_invalid_single_threaded[] ;
extern const char warn_invalid_report_style[] ;
extern const char warn_invalid_report[] ;
#endif
@@ -0,0 +1,41 @@
#include "iperf_config.h"
#if defined(HAVE_PTHREAD) && defined(__ANDROID__)
/* Workaround for `pthread_cancel()` in Android, using `pthread_kill()` instead,
* as Android NDK does not support `pthread_cancel()`.
*/
#include <string.h>
#include <signal.h>
#include "iperf_pthread.h"
void iperf_thread_exit_handler(int sig)
{
pthread_exit(0);
}
int iperf_set_thread_exit_handler() {
int rc;
struct sigaction actions;
memset(&actions, 0, sizeof(actions));
sigemptyset(&actions.sa_mask);
actions.sa_flags = 0;
actions.sa_handler = iperf_thread_exit_handler;
rc = sigaction(SIGUSR1, &actions, NULL);
return rc;
}
int pthread_setcanceltype(int type, int *oldtype) { return 0; }
int pthread_setcancelstate(int state, int *oldstate) { return 0; }
int pthread_cancel(pthread_t thread_id) {
int status;
if ((status = iperf_set_thread_exit_handler()) == 0) {
status = pthread_kill(thread_id, SIGUSR1);
}
return status;
}
#endif // defined(HAVE_PTHREAD) && defined(__ANDROID__)
@@ -0,0 +1,21 @@
#include "iperf_config.h"
#if defined(HAVE_PTHREAD)
#include <pthread.h>
#if defined(__ANDROID__)
/* Adding missing `pthread` related definitions in Android.
*/
#define PTHREAD_CANCEL_ASYNCHRONOUS 0
#define PTHREAD_CANCEL_ENABLE 0
int pthread_setcanceltype(int type, int *oldtype);
int pthread_setcancelstate(int state, int *oldstate);
int pthread_cancel(pthread_t thread_id);
#endif // defined(__ANDROID__)
#endif // defined(HAVE_PTHREAD)
+778
View File
@@ -0,0 +1,778 @@
/*
* iperf, Copyright (c) 2014-2019, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include "iperf_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/select.h>
#include <limits.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_sctp.h"
#include "net.h"
/* iperf_sctp_recv
*
* receives the data for SCTP
*/
int
iperf_sctp_recv(struct iperf_stream *sp)
{
#if defined(HAVE_SCTP_H)
int r;
r = Nread_no_select(sp->socket, sp->buffer, sp->settings->blksize, Psctp);
if (r < 0)
return r;
/* Only count bytes received while we're in the correct state. */
if (sp->test->state == TEST_RUNNING) {
sp->result->bytes_received += r;
sp->result->bytes_received_this_interval += r;
}
else {
if (sp->test->debug)
printf("Late receive, state = %d\n", sp->test->state);
}
return r;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_send
*
* sends the data for SCTP
*/
int
iperf_sctp_send(struct iperf_stream *sp)
{
#if defined(HAVE_SCTP_H)
int r;
r = Nwrite(sp->socket, sp->buffer, sp->settings->blksize, Psctp);
if (r < 0)
return r;
sp->result->bytes_sent += r;
sp->result->bytes_sent_this_interval += r;
return r;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_accept
*
* accept a new SCTP stream connection
*/
int
iperf_sctp_accept(struct iperf_test * test)
{
#if defined(HAVE_SCTP_H)
int s;
signed char rbuf = ACCESS_DENIED;
char cookie[COOKIE_SIZE];
socklen_t len;
struct sockaddr_storage addr;
len = sizeof(addr);
s = accept(test->listener, (struct sockaddr *) &addr, &len);
if (s < 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
if (Nread(s, cookie, COOKIE_SIZE, Psctp) < 0) {
i_errno = IERECVCOOKIE;
close(s);
return -1;
}
if (strncmp(test->cookie, cookie, COOKIE_SIZE) != 0) {
if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Psctp) < 0) {
i_errno = IESENDMESSAGE;
close(s);
return -1;
}
close(s);
}
return s;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_listen
*
* start up a listener for SCTP stream connections
*/
int
iperf_sctp_listen(struct iperf_test *test)
{
#if defined(HAVE_SCTP_H)
struct addrinfo hints, *res;
char portstr[6];
int s, opt, saved_errno;
close(test->listener);
test->listener = -1;
snprintf(portstr, 6, "%d", test->server_port);
memset(&hints, 0, sizeof(hints));
/*
* If binding to the wildcard address with no explicit address
* family specified, then force us to get an AF_INET6 socket.
* More details in the comments in netanounce().
*/
if (test->settings->domain == AF_UNSPEC && !test->bind_address) {
hints.ai_family = AF_INET6;
} else {
hints.ai_family = test->settings->domain;
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((gerror = getaddrinfo(test->bind_address, portstr, &hints, &res)) != 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
if ((s = socket(res->ai_family, SOCK_STREAM, IPPROTO_SCTP)) < 0) {
freeaddrinfo(res);
i_errno = IESTREAMLISTEN;
return -1;
}
if ((opt = test->settings->socket_bufsize)) {
int saved_errno;
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
}
if (test->bind_dev) {
#if defined(SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif // SO_BINDTODEVICE
{
saved_errno = errno;
close(s);
freeaddrinfo(res);
i_errno = IEBINDDEV;
errno = saved_errno;
return -1;
}
}
#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
if (res->ai_family == AF_INET6 && (test->settings->domain == AF_UNSPEC ||
test->settings->domain == AF_INET6)) {
if (test->settings->domain == AF_UNSPEC)
opt = 0;
else
opt = 1;
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *) &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IEPROTOCOL;
return -1;
}
}
#endif /* IPV6_V6ONLY */
opt = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IEREUSEADDR;
return -1;
}
/* servers must call sctp_bindx() _instead_ of bind() */
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
if (iperf_sctp_bindx(test, s, IPERF_SCTP_SERVER)) {
close(s);
freeaddrinfo(res);
return -1;
}
} else
if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESTREAMLISTEN;
return -1;
}
freeaddrinfo(res);
if (listen(s, INT_MAX) < 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
test->listener = s;
return s;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_connect
*
* connect to a SCTP stream listener
*/
int
iperf_sctp_connect(struct iperf_test *test)
{
#if defined(HAVE_SCTP_H)
int s, opt, saved_errno;
char portstr[6];
struct addrinfo hints, *local_res = NULL, *server_res = NULL;
if (test->bind_address) {
memset(&hints, 0, sizeof(hints));
hints.ai_family = test->settings->domain;
hints.ai_socktype = SOCK_STREAM;
if ((gerror = getaddrinfo(test->bind_address, NULL, &hints, &local_res)) != 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = test->settings->domain;
hints.ai_socktype = SOCK_STREAM;
snprintf(portstr, sizeof(portstr), "%d", test->server_port);
if ((gerror = getaddrinfo(test->server_hostname, portstr, &hints, &server_res)) != 0) {
if (test->bind_address)
freeaddrinfo(local_res);
i_errno = IESTREAMCONNECT;
return -1;
}
s = socket(server_res->ai_family, SOCK_STREAM, IPPROTO_SCTP);
if (s < 0) {
freeaddrinfo(local_res);
freeaddrinfo(server_res);
i_errno = IESTREAMCONNECT;
return -1;
}
if ((opt = test->settings->socket_bufsize)) {
int saved_errno;
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
}
if (test->bind_dev) {
#if defined(SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif // SO_BINDTODEVICE
{
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
i_errno = IEBINDDEV;
errno = saved_errno;
return -1;
}
}
/*
* Various ways to bind the local end of the connection.
* 1. --bind (with or without --cport).
*/
if (test->bind_address) {
struct sockaddr_in *lcladdr;
lcladdr = (struct sockaddr_in *)local_res->ai_addr;
lcladdr->sin_port = htons(test->bind_port);
if (bind(s, (struct sockaddr *) local_res->ai_addr, local_res->ai_addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESTREAMCONNECT;
return -1;
}
freeaddrinfo(local_res);
}
/* --cport, no --bind */
else if (test->bind_port) {
size_t addrlen;
struct sockaddr_storage lcl;
/* IPv4 */
if (server_res->ai_family == AF_INET) {
struct sockaddr_in *lcladdr = (struct sockaddr_in *) &lcl;
lcladdr->sin_family = AF_INET;
lcladdr->sin_port = htons(test->bind_port);
lcladdr->sin_addr.s_addr = INADDR_ANY;
addrlen = sizeof(struct sockaddr_in);
}
/* IPv6 */
else if (server_res->ai_family == AF_INET6) {
struct sockaddr_in6 *lcladdr = (struct sockaddr_in6 *) &lcl;
lcladdr->sin6_family = AF_INET6;
lcladdr->sin6_port = htons(test->bind_port);
lcladdr->sin6_addr = in6addr_any;
addrlen = sizeof(struct sockaddr_in6);
}
/* Unknown protocol */
else {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IEPROTOCOL;
return -1;
}
if (bind(s, (struct sockaddr *) &lcl, addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESTREAMCONNECT;
return -1;
}
}
if (test->no_delay != 0) {
opt = 1;
if (setsockopt(s, IPPROTO_SCTP, SCTP_NODELAY, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETNODELAY;
return -1;
}
}
if ((test->settings->mss >= 512 && test->settings->mss <= 131072)) {
/*
* Some platforms use a struct sctp_assoc_value as the
* argument to SCTP_MAXSEG. Other (older API implementations)
* take an int. FreeBSD 10 and CentOS 6 support SCTP_MAXSEG,
* but OpenSolaris 11 doesn't.
*/
#ifdef HAVE_STRUCT_SCTP_ASSOC_VALUE
struct sctp_assoc_value av;
/*
* Some platforms support SCTP_FUTURE_ASSOC, others need to
* (equivalently) do 0 here. FreeBSD 10 is an example of the
* former, CentOS 6 Linux is an example of the latter.
*/
#ifdef SCTP_FUTURE_ASSOC
av.assoc_id = SCTP_FUTURE_ASSOC;
#else
av.assoc_id = 0;
#endif
av.assoc_value = test->settings->mss;
if (setsockopt(s, IPPROTO_SCTP, SCTP_MAXSEG, &av, sizeof(av)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETMSS;
return -1;
}
#else
opt = test->settings->mss;
/*
* Solaris might not support this option. If it doesn't work,
* ignore the error (at least for now).
*/
if (setsockopt(s, IPPROTO_SCTP, SCTP_MAXSEG, &opt, sizeof(opt)) < 0 &&
errno != ENOPROTOOPT) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETMSS;
return -1;
}
#endif /* HAVE_STRUCT_SCTP_ASSOC_VALUE */
}
if (test->settings->num_ostreams > 0) {
struct sctp_initmsg initmsg;
memset(&initmsg, 0, sizeof(struct sctp_initmsg));
initmsg.sinit_num_ostreams = test->settings->num_ostreams;
if (setsockopt(s, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETSCTPNSTREAM;
return -1;
}
}
/* clients must call bind() followed by sctp_bindx() before connect() */
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
if (iperf_sctp_bindx(test, s, IPERF_SCTP_CLIENT)) {
freeaddrinfo(server_res);
close(s);
return -1;
}
}
/* TODO support sctp_connectx() to avoid heartbeating. */
if (connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen) < 0 && errno != EINPROGRESS) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESTREAMCONNECT;
return -1;
}
/* Send cookie for verification */
if (Nwrite(s, test->cookie, COOKIE_SIZE, Psctp) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESENDCOOKIE;
return -1;
}
/*
* We want to allow fragmentation. But there's at least one
* implementation (Solaris) that doesn't support this option,
* even though it defines SCTP_DISABLE_FRAGMENTS. So we have to
* try setting the option and ignore the error, if it doesn't
* work.
*/
opt = 0;
if (setsockopt(s, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &opt, sizeof(opt)) < 0 &&
errno != ENOPROTOOPT) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETSCTPDISABLEFRAG;
return -1;
}
freeaddrinfo(server_res);
return s;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
int
iperf_sctp_init(struct iperf_test *test)
{
#if defined(HAVE_SCTP_H)
return 0;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_bindx
*
* handle binding to multiple endpoints (-X parameters)
*/
int
iperf_sctp_bindx(struct iperf_test *test, int s, int is_server)
{
#if defined(HAVE_SCTP_H)
struct addrinfo hints;
char portstr[6];
char *servname;
struct addrinfo *ai, *ai0;
struct sockaddr *xaddrs;
struct xbind_entry *xbe, *xbe0;
char *bp;
size_t xaddrlen;
int nxaddrs;
int retval;
int domain;
int saved_errno;
domain = test->settings->domain;
xbe0 = NULL;
retval = 0;
if (TAILQ_EMPTY(&test->xbind_addrs))
return retval; /* nothing to do */
memset(&hints, 0, sizeof(hints));
hints.ai_family = (domain == AF_UNSPEC ? AF_INET6 : domain);
hints.ai_socktype = SOCK_STREAM;
servname = NULL;
if (is_server) {
hints.ai_flags |= AI_PASSIVE;
snprintf(portstr, 6, "%d", test->server_port);
servname = portstr;
}
/* client: must pop first -X address and call bind().
* sctp_bindx() must see the ephemeral port chosen by bind().
* we deliberately ignore the -B argument in this case.
*/
if (!is_server) {
struct sockaddr *sa;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
int eport;
xbe0 = TAILQ_FIRST(&test->xbind_addrs);
TAILQ_REMOVE(&test->xbind_addrs, xbe0, link);
if ((gerror = getaddrinfo(xbe0->name, servname, &hints, &xbe0->ai)) != 0) {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
ai = xbe0->ai;
if (domain != AF_UNSPEC && domain != ai->ai_family) {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
if (bind(s, (struct sockaddr *)ai->ai_addr, ai->ai_addrlen) < 0) {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
/* if only one -X argument, nothing more to do */
if (TAILQ_EMPTY(&test->xbind_addrs))
goto out;
sa = (struct sockaddr *)ai->ai_addr;
if (sa->sa_family == AF_INET) {
sin = (struct sockaddr_in *)ai->ai_addr;
eport = sin->sin_port;
} else if (sa->sa_family == AF_INET6) {
sin6 = (struct sockaddr_in6 *)ai->ai_addr;
eport = sin6->sin6_port;
} else {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
snprintf(portstr, 6, "%d", ntohs(eport));
servname = portstr;
}
/* pass 1: resolve and compute lengths. */
nxaddrs = 0;
xaddrlen = 0;
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
if (xbe->ai != NULL)
freeaddrinfo(xbe->ai);
if ((gerror = getaddrinfo(xbe->name, servname, &hints, &xbe->ai)) != 0) {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
ai0 = xbe->ai;
for (ai = ai0; ai; ai = ai->ai_next) {
if (domain != AF_UNSPEC && domain != ai->ai_family)
continue;
xaddrlen += ai->ai_addrlen;
++nxaddrs;
}
}
/* pass 2: copy into flat buffer. */
xaddrs = (struct sockaddr *)malloc(xaddrlen);
if (!xaddrs) {
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
bp = (char *)xaddrs;
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
ai0 = xbe->ai;
for (ai = ai0; ai; ai = ai->ai_next) {
if (domain != AF_UNSPEC && domain != ai->ai_family)
continue;
memcpy(bp, ai->ai_addr, ai->ai_addrlen);
bp += ai->ai_addrlen;
}
}
if (sctp_bindx(s, xaddrs, nxaddrs, SCTP_BINDX_ADD_ADDR) == -1) {
saved_errno = errno;
close(s);
free(xaddrs);
errno = saved_errno;
i_errno = IESETSCTPBINDX;
retval = -1;
goto out;
}
free(xaddrs);
retval = 0;
out:
/* client: put head node back. */
if (!is_server && xbe0)
TAILQ_INSERT_HEAD(&test->xbind_addrs, xbe0, link);
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
if (xbe->ai) {
freeaddrinfo(xbe->ai);
xbe->ai = NULL;
}
}
return retval;
#else
i_errno = IENOSCTP;
return -1;
#endif /* HAVE_SCTP_H */
}
/* iperf_sctp_get_rtt
*
* Get SCTP stream RTT.
* Assuming that iperf3 supports only one-toone SCTP associassion, and not one-to-many associassion.
*
* Main resouses used are RFC-6458, man pages for SCTP,
* https://docs.oracle.com/cd/E19253-01/817-4415/sockets-199/index.html.
*
*/
int
iperf_sctp_get_info(struct iperf_stream *sp, struct iperf_sctp_info *sctp_info)
{
#if defined(HAVE_SCTP_H)
struct sctp_status status;
socklen_t len;
sctp_assoc_t assoc_id;
int rc = 0;
if (sp->test->protocol->id != Psctp) {
rc = -1;
} else {
#ifdef SCTP_FUTURE_ASSOC
assoc_id = SCTP_FUTURE_ASSOC;
#else
assoc_id = 0;
#endif
len = sizeof(status);
rc = sctp_opt_info(sp->socket, assoc_id, SCTP_STATUS, &status, &len);
if (rc < 0) {
if (sp->test->debug_level >= DEBUG_LEVEL_ERROR)
iperf_err(sp->test, "sctp_opt_info get SCTP_STATUS for socket %d failed with errno %d - %s", sp->socket, errno, strerror(errno));
} else {
sctp_info->wnd = status.sstat_rwnd;
sctp_info->rtt = status.sstat_primary.spinfo_srtt;
sctp_info->pmtu = status.sstat_primary.spinfo_mtu;
sctp_info->cwnd = status.sstat_primary.spinfo_cwnd;
}
}
return rc;
#else
return -1;
#endif /* HAVE_SCTP_H */
}
@@ -0,0 +1,74 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef IPERF_SCTP_H
#define IPERF_SCTP_H
#ifdef HAVE_NETINET_SCTP_H
#include <netinet/sctp.h>
#endif /* HAVE_NETINET_SCTP_H */
/**
* iperf_sctp_accept -- accepts a new SCTP connection
* on sctp_listener_socket for SCTP data and param/result
* exchange messages
*returns 0 on success
*
*/
int iperf_sctp_accept(struct iperf_test *);
/**
* iperf_sctp_recv -- receives the data for sctp
* and the Param/result message exchange
*returns state of packet received
*
*/
int iperf_sctp_recv(struct iperf_stream *);
/**
* iperf_sctp_send -- sends the client data for sctp
* and the Param/result message exchanges
* returns: bytes sent
*
*/
int iperf_sctp_send(struct iperf_stream *);
int iperf_sctp_listen(struct iperf_test *);
int iperf_sctp_connect(struct iperf_test *);
int iperf_sctp_init(struct iperf_test *test);
#define IPERF_SCTP_CLIENT 0
#define IPERF_SCTP_SERVER 1
int iperf_sctp_bindx(struct iperf_test *test, int s, int is_server);
int iperf_sctp_get_info(struct iperf_stream *sp, struct iperf_sctp_info *sctp_info);
#endif
@@ -0,0 +1,959 @@
/*
* iperf, Copyright (c) 2014-2023 The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
/* iperf_server_api.c: Functions to be used by an iperf server
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include <setjmp.h>
#include <signal.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_udp.h"
#include "iperf_tcp.h"
#include "iperf_util.h"
#include "timer.h"
#include "iperf_time.h"
#include "net.h"
#include "units.h"
#include "iperf_util.h"
#include "iperf_locale.h"
#if defined(HAVE_TCP_CONGESTION)
#if !defined(TCP_CA_NAME_MAX)
#define TCP_CA_NAME_MAX 16
#endif /* TCP_CA_NAME_MAX */
#endif /* HAVE_TCP_CONGESTION */
void *
iperf_server_worker_run(void *s) {
struct iperf_stream *sp = (struct iperf_stream *) s;
struct iperf_test *test = sp->test;
/* Blocking signal to make sure that signal will be handled by main thread */
sigset_t set;
sigemptyset(&set);
#ifdef SIGTERM
sigaddset(&set, SIGTERM);
#endif
#ifdef SIGHUP
sigaddset(&set, SIGHUP);
#endif
#ifdef SIGINT
sigaddset(&set, SIGINT);
#endif
if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {
i_errno = IEPTHREADSIGMASK;
goto cleanup_and_fail;
}
/* Allow this thread to be cancelled even if it's in a syscall */
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (! (test->done) && ! (sp->done)) {
if (sp->sender) {
if (iperf_send_mt(sp) < 0) {
goto cleanup_and_fail;
}
}
else {
if (iperf_recv_mt(sp) < 0) {
goto cleanup_and_fail;
}
}
}
return NULL;
cleanup_and_fail:
return NULL;
}
int
iperf_server_listen(struct iperf_test *test)
{
retry:
if((test->listener = netannounce(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
if (errno == EAFNOSUPPORT && (test->settings->domain == AF_INET6 || test->settings->domain == AF_UNSPEC)) {
/* If we get "Address family not supported by protocol", that
** probably means we were compiled with IPv6 but the running
** kernel does not actually do IPv6. This is not too unusual,
** v6 support is and perhaps always will be spotty.
*/
warning("this system does not seem to support IPv6 - trying IPv4");
test->settings->domain = AF_INET;
goto retry;
} else {
i_errno = IELISTEN;
return -1;
}
}
if (!test->json_output) {
if (test->server_last_run_rc != 2)
test->server_test_number +=1;
if (test->debug || test->server_last_run_rc != 2) {
iperf_printf(test, "-----------------------------------------------------------\n");
iperf_printf(test, "Server listening on %d (test #%d)\n", test->server_port, test->server_test_number);
iperf_printf(test, "-----------------------------------------------------------\n");
if (test->forceflush)
iflush(test);
}
}
FD_ZERO(&test->read_set);
FD_ZERO(&test->write_set);
FD_SET(test->listener, &test->read_set);
if (test->listener > test->max_fd) test->max_fd = test->listener;
return 0;
}
int
iperf_accept(struct iperf_test *test)
{
int s;
int ret = -1;
signed char rbuf = ACCESS_DENIED;
socklen_t len;
struct sockaddr_storage addr;
len = sizeof(addr);
if ((s = accept(test->listener, (struct sockaddr *) &addr, &len)) < 0) {
i_errno = IEACCEPT;
return ret;
}
if (test->ctrl_sck == -1) {
/* Server free, accept new client */
test->ctrl_sck = s;
// set TCP_NODELAY for lower latency on control messages
int flag = 1;
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
i_errno = IESETNODELAY;
goto error_handling;
}
#if defined(HAVE_TCP_USER_TIMEOUT)
int opt;
if ((opt = test->settings->snd_timeout)) {
if (setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
i_errno = IESETUSERTIMEOUT;
goto error_handling;
}
}
#endif /* HAVE_TCP_USER_TIMEOUT */
#if defined (HAVE_TCP_KEEPALIVE)
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
if (iperf_set_control_keepalive(test) < 0)
return -1;
#endif //HAVE_TCP_KEEPALIVE
if (Nread(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) != COOKIE_SIZE) {
/*
* Note this error covers both the case of a system error
* or the inability to read the correct amount of data
* (i.e. timed out).
*/
i_errno = IERECVCOOKIE;
goto error_handling;
}
FD_SET(test->ctrl_sck, &test->read_set);
if (test->ctrl_sck > test->max_fd) test->max_fd = test->ctrl_sck;
if (iperf_set_send_state(test, PARAM_EXCHANGE) != 0)
goto error_handling;
if (iperf_exchange_parameters(test) < 0)
goto error_handling;
if (test->server_affinity != -1) {
if (iperf_setaffinity(test, test->server_affinity) != 0)
goto error_handling;
}
if (test->on_connect)
test->on_connect(test);
} else {
/*
* Don't try to read from the socket. It could block an ongoing test.
* Just send ACCESS_DENIED.
* Also, if sending failed, don't return an error, as the request is not related
* to the ongoing test, and returning an error will terminate the test.
*/
if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Ptcp) < 0) {
if (test->debug)
printf("failed to send ACCESS_DENIED to an unsolicited connection request during active test\n");
} else {
if (test->debug)
printf("successfully sent ACCESS_DENIED to an unsolicited connection request during active test\n");
}
close(s);
}
return 0;
error_handling:
close(s);
return ret;
}
/**************************************************************************/
int
iperf_handle_message_server(struct iperf_test *test)
{
int rval;
struct iperf_stream *sp;
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Reading new State from the Client - current state is %d-%s\n", test->state, state_to_text(test->state));
}
// XXX: Need to rethink how this behaves to fit API
if ((rval = Nread(test->ctrl_sck, (char*) &test->state, sizeof(signed char), Ptcp)) <= 0) {
if (rval == 0) {
iperf_err(test, "the client has unexpectedly closed the connection");
i_errno = IECTRLCLOSE;
iperf_set_test_state(test, IPERF_DONE);
return 0;
} else {
i_errno = IERECVMESSAGE;
return -1;
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "State change: server received and changed State to %d-%s\n", test->state, state_to_text(test->state));
}
switch(test->state) {
case TEST_START:
break;
case TEST_END:
test->done = 1;
cpu_util(test->cpu_util);
test->stats_callback(test);
SLIST_FOREACH(sp, &test->streams, streams) {
FD_CLR(sp->socket, &test->read_set);
FD_CLR(sp->socket, &test->write_set);
close(sp->socket);
}
test->reporter_callback(test);
if (iperf_set_send_state(test, EXCHANGE_RESULTS) != 0)
return -1;
if (iperf_exchange_results(test) < 0)
return -1;
if (iperf_set_send_state(test, DISPLAY_RESULTS) != 0)
return -1;
if (test->on_test_finish)
test->on_test_finish(test);
break;
case IPERF_DONE:
break;
case CLIENT_TERMINATE:
i_errno = IECLIENTTERM;
// Temporarily be in DISPLAY_RESULTS phase so we can get
// ending summary statistics.
signed char oldstate = test->state;
cpu_util(test->cpu_util);
test->state = DISPLAY_RESULTS;
test->reporter_callback(test);
test->state = oldstate;
// XXX: Remove this line below!
iperf_err(test, "the client has terminated");
SLIST_FOREACH(sp, &test->streams, streams) {
FD_CLR(sp->socket, &test->read_set);
FD_CLR(sp->socket, &test->write_set);
close(sp->socket);
}
iperf_set_test_state(test, IPERF_DONE);
break;
default:
i_errno = IEMESSAGE;
return -1;
}
return 0;
}
static void
server_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
struct iperf_stream *sp;
test->timer = NULL;
if (test->done)
return;
test->done = 1;
/* Free streams */
while (!SLIST_EMPTY(&test->streams)) {
sp = SLIST_FIRST(&test->streams);
SLIST_REMOVE_HEAD(&test->streams, streams);
close(sp->socket);
iperf_free_stream(sp);
}
close(test->ctrl_sck);
test->ctrl_sck = -1;
}
static void
server_stats_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
if (test->done)
return;
if (test->stats_callback)
test->stats_callback(test);
}
static void
server_reporter_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
if (test->done)
return;
if (test->reporter_callback)
test->reporter_callback(test);
}
static int
create_server_timers(struct iperf_test * test)
{
struct iperf_time now;
TimerClientData cd;
int max_rtt = 4; /* seconds */
int state_transitions = 10; /* number of state transitions in iperf3 */
int grace_period = max_rtt * state_transitions;
if (iperf_time_now(&now) < 0) {
i_errno = IEINITTEST;
return -1;
}
cd.p = test;
test->timer = test->stats_timer = test->reporter_timer = NULL;
if (test->duration != 0 ) {
test->done = 0;
test->timer = tmr_create(&now, server_timer_proc, cd, (test->duration + test->omit + grace_period) * SEC_TO_US, 0);
if (test->timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
test->stats_timer = test->reporter_timer = NULL;
if (test->stats_interval != 0) {
test->stats_timer = tmr_create(&now, server_stats_timer_proc, cd, test->stats_interval * SEC_TO_US, 1);
if (test->stats_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
if (test->reporter_interval != 0) {
test->reporter_timer = tmr_create(&now, server_reporter_timer_proc, cd, test->reporter_interval * SEC_TO_US, 1);
if (test->reporter_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
return 0;
}
static void
server_omit_timer_proc(TimerClientData client_data, struct iperf_time *nowP)
{
struct iperf_test *test = client_data.p;
test->omit_timer = NULL;
test->omitting = 0;
iperf_reset_stats(test);
if (test->verbose && !test->json_output && test->reporter_interval == 0)
iperf_printf(test, "%s", report_omit_done);
/* Reset the timers. */
if (test->stats_timer != NULL)
tmr_reset(nowP, test->stats_timer);
if (test->reporter_timer != NULL)
tmr_reset(nowP, test->reporter_timer);
}
static int
create_server_omit_timer(struct iperf_test * test)
{
struct iperf_time now;
TimerClientData cd;
if (test->omit == 0) {
test->omit_timer = NULL;
test->omitting = 0;
} else {
if (iperf_time_now(&now) < 0) {
i_errno = IEINITTEST;
return -1;
}
test->omitting = 1;
cd.p = test;
test->omit_timer = tmr_create(&now, server_omit_timer_proc, cd, test->omit * SEC_TO_US, 0);
if (test->omit_timer == NULL) {
i_errno = IEINITTEST;
return -1;
}
}
return 0;
}
static void
cleanup_server(struct iperf_test *test)
{
struct iperf_stream *sp;
/* Cancel outstanding threads */
int i_errno_save = i_errno;
SLIST_FOREACH(sp, &test->streams, streams) {
int rc;
sp->done = 1;
if (sp->thread_created == 1) {
rc = pthread_cancel(sp->thr);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADCANCEL;
errno = rc;
iperf_err(test, "cleanup_server in pthread_cancel - %s", iperf_strerror(i_errno));
}
rc = pthread_join(sp->thr, NULL);
if (rc != 0 && rc != ESRCH) {
i_errno = IEPTHREADJOIN;
errno = rc;
iperf_err(test, "cleanup_server in pthread_join - %s", iperf_strerror(i_errno));
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d stopped\n", sp->socket);
}
sp->thread_created = 0;
}
}
i_errno = i_errno_save;
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "All threads stopped\n");
}
/* Close open streams */
SLIST_FOREACH(sp, &test->streams, streams) {
if (sp->socket > -1) {
FD_CLR(sp->socket, &test->read_set);
FD_CLR(sp->socket, &test->write_set);
close(sp->socket);
sp->socket = -1;
}
}
/* Close open test sockets */
if (test->ctrl_sck > -1) {
close(test->ctrl_sck);
test->ctrl_sck = -1;
}
if (test->listener > -1) {
close(test->listener);
test->listener = -1;
}
if (test->prot_listener > -1) { // May remain open if create socket failed
close(test->prot_listener);
test->prot_listener = -1;
}
/* Cancel any remaining timers. */
if (test->stats_timer != NULL) {
tmr_cancel(test->stats_timer);
test->stats_timer = NULL;
}
if (test->reporter_timer != NULL) {
tmr_cancel(test->reporter_timer);
test->reporter_timer = NULL;
}
if (test->omit_timer != NULL) {
tmr_cancel(test->omit_timer);
test->omit_timer = NULL;
}
if (test->congestion_used != NULL) {
free(test->congestion_used);
test->congestion_used = NULL;
}
if (test->timer != NULL) {
tmr_cancel(test->timer);
test->timer = NULL;
}
}
int
iperf_run_server(struct iperf_test *test)
{
int result, s;
int send_streams_accepted, rec_streams_accepted;
int streams_to_send = 0, streams_to_rec = 0;
#if defined(HAVE_TCP_CONGESTION)
int saved_errno;
#endif /* HAVE_TCP_CONGESTION */
fd_set read_set, write_set;
struct iperf_stream *sp;
struct iperf_time now;
struct iperf_time last_receive_time;
struct iperf_time diff_time;
struct timeval* timeout;
struct timeval used_timeout;
iperf_size_t last_receive_blocks;
int flag;
int64_t t_usecs;
int64_t timeout_us;
int64_t rcv_timeout_us;
if (test->logfile) {
if (iperf_open_logfile(test) < 0)
return -2;
}
if (test->affinity != -1) {
if (iperf_setaffinity(test, test->affinity) != 0) {
cleanup_server(test);
return -2;
}
}
if (test->json_output) {
if (iperf_json_start(test) < 0) {
cleanup_server(test);
return -2;
}
}
if (test->json_output) {
cJSON_AddItemToObject(test->json_start, "version", cJSON_CreateString(version));
cJSON_AddItemToObject(test->json_start, "system_info", cJSON_CreateString(get_system_info()));
} else if (test->verbose) {
iperf_printf(test, "%s\n", version);
iperf_printf(test, "%s", "");
iperf_printf(test, "%s\n", get_system_info());
iflush(test);
}
// Open socket and listen
if (iperf_server_listen(test) < 0) {
cleanup_server(test);
return -2;
}
iperf_time_now(&last_receive_time); // Initialize last time something was received
last_receive_blocks = 0;
iperf_set_test_state(test, IPERF_START);
send_streams_accepted = 0;
rec_streams_accepted = 0;
rcv_timeout_us = (test->settings->rcv_timeout.secs * SEC_TO_US) + test->settings->rcv_timeout.usecs;
while (test->state != IPERF_DONE) {
// Check if average transfer rate was exceeded (condition set in the callback routines)
if (test->bitrate_limit_exceeded) {
cleanup_server(test);
i_errno = IETOTALRATE;
return -1;
}
memcpy(&read_set, &test->read_set, sizeof(fd_set));
memcpy(&write_set, &test->write_set, sizeof(fd_set));
iperf_time_now(&now);
timeout = tmr_timeout(&now);
// Ensure select() will timeout to allow handling error cases that require server restart
if (test->state == IPERF_START) { // In idle mode server may need to restart
if (timeout == NULL && test->settings->idle_timeout > 0) {
used_timeout.tv_sec = test->settings->idle_timeout;
used_timeout.tv_usec = 0;
timeout = &used_timeout;
}
} else if (test->mode != SENDER) { // In non-reverse active mode server ensures data is received
timeout_us = -1;
if (timeout != NULL) {
used_timeout.tv_sec = timeout->tv_sec;
used_timeout.tv_usec = timeout->tv_usec;
timeout_us = (timeout->tv_sec * SEC_TO_US) + timeout->tv_usec;
}
/* Cap the maximum select timeout at 1 second */
if (timeout_us > SEC_TO_US) {
timeout_us = SEC_TO_US;
}
if (timeout_us < 0 || timeout_us > rcv_timeout_us) {
used_timeout.tv_sec = test->settings->rcv_timeout.secs;
used_timeout.tv_usec = test->settings->rcv_timeout.usecs;
}
timeout = &used_timeout;
}
result = select(test->max_fd + 1, &read_set, &write_set, NULL, timeout);
if (result < 0 && errno != EINTR) {
cleanup_server(test);
i_errno = IESELECT;
return -1;
} else if (result == 0) {
/*
* If nothing was received during the specified time (per
* state) then probably something got stuck either at the
* client, server or network, and test should be forced to
* end.
*/
iperf_time_now(&now);
t_usecs = 0;
if (iperf_time_diff(&now, &last_receive_time, &diff_time) == 0) {
t_usecs = iperf_time_in_usecs(&diff_time);
/* We're in the state where we're still accepting connections */
if (test->state == IPERF_START) {
if (test->settings->idle_timeout > 0 && t_usecs >= test->settings->idle_timeout * SEC_TO_US) {
test->server_forced_idle_restarts_count += 1;
if (test->debug)
printf("Server restart (#%d) in idle state as no connection request was received for %d sec\n",
test->server_forced_idle_restarts_count, test->settings->idle_timeout);
cleanup_server(test);
if ( iperf_get_test_one_off(test) ) {
if (test->debug)
printf("No connection request was received for %d sec in one-off mode; exiting.\n",
test->settings->idle_timeout);
exit(0);
}
return 2;
}
}
/*
* Running a test. If we're receiving, be sure we're making
* progress (sender hasn't died/crashed).
*/
else if (test->mode != SENDER && t_usecs > rcv_timeout_us) {
/* Idle timeout if no new blocks received */
if (test->blocks_received == last_receive_blocks) {
test->server_forced_no_msg_restarts_count += 1;
i_errno = IENOMSG;
if (iperf_get_verbose(test))
iperf_err(test, "Server restart (#%d) during active test due to idle timeout for receiving data",
test->server_forced_no_msg_restarts_count);
cleanup_server(test);
return -1;
}
}
}
}
/* See if the test is making progress */
if (test->blocks_received > last_receive_blocks) {
last_receive_blocks = test->blocks_received;
last_receive_time = now;
}
if (result > 0) {
if (FD_ISSET(test->listener, &read_set)) {
if (test->state != CREATE_STREAMS) {
if (iperf_accept(test) < 0) {
cleanup_server(test);
return -1;
}
FD_CLR(test->listener, &read_set);
// Set streams number
if (test->mode == BIDIRECTIONAL) {
streams_to_send = test->num_streams;
streams_to_rec = test->num_streams;
} else if (test->mode == RECEIVER) {
streams_to_rec = test->num_streams;
streams_to_send = 0;
} else {
streams_to_send = test->num_streams;
streams_to_rec = 0;
}
}
}
if (FD_ISSET(test->ctrl_sck, &read_set)) {
if (iperf_handle_message_server(test) < 0) {
cleanup_server(test);
return -1;
}
FD_CLR(test->ctrl_sck, &read_set);
}
if (test->state == CREATE_STREAMS) {
if (FD_ISSET(test->prot_listener, &read_set)) {
if ((s = test->protocol->accept(test)) < 0) {
cleanup_server(test);
return -1;
}
/* apply other common socket options */
if (iperf_common_sockopts(test, s) < 0)
{
cleanup_server(test);
return -1;
}
if (!is_closed(s)) {
#if defined(HAVE_TCP_USER_TIMEOUT)
if (test->protocol->id == Ptcp) {
int opt;
if ((opt = test->settings->snd_timeout)) {
if (setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
cleanup_server(test);
errno = saved_errno;
i_errno = IESETUSERTIMEOUT;
return -1;
}
}
}
#endif /* HAVE_TCP_USER_TIMEOUT */
#if defined(HAVE_TCP_CONGESTION)
if (test->protocol->id == Ptcp) {
if (test->congestion) {
if (setsockopt(s, IPPROTO_TCP, TCP_CONGESTION, test->congestion, strlen(test->congestion)) < 0) {
/*
* ENOENT means we tried to set the
* congestion algorithm but the algorithm
* specified doesn't exist. This can happen
* if the client and server have different
* congestion algorithms available. In this
* case, print a warning, but otherwise
* continue.
*/
if (errno == ENOENT) {
warning("TCP congestion control algorithm not supported");
}
else {
saved_errno = errno;
close(s);
cleanup_server(test);
errno = saved_errno;
i_errno = IESETCONGESTION;
return -1;
}
}
}
{
socklen_t len = TCP_CA_NAME_MAX;
char ca[TCP_CA_NAME_MAX + 1];
int rc;
rc = getsockopt(s, IPPROTO_TCP, TCP_CONGESTION, ca, &len);
if (rc < 0 && test->congestion) {
saved_errno = errno;
close(s);
cleanup_server(test);
errno = saved_errno;
i_errno = IESETCONGESTION;
return -1;
}
/*
* If not the first connection, discard prior
* congestion algorithm name so we don't leak
* duplicated strings. We probably don't need
* the old string anyway.
*/
if (test->congestion_used != NULL) {
free(test->congestion_used);
}
// Set actual used congestion alg, or set to unknown if could not get it
if (rc < 0)
test->congestion_used = strdup("unknown");
else
test->congestion_used = strdup(ca);
if (test->debug) {
printf("Congestion algorithm is %s\n", test->congestion_used);
}
}
}
#endif /* HAVE_TCP_CONGESTION */
if (rec_streams_accepted != streams_to_rec) {
flag = 0;
++rec_streams_accepted;
} else if (send_streams_accepted != streams_to_send) {
flag = 1;
++send_streams_accepted;
}
if (flag != -1) {
sp = iperf_new_stream(test, s, flag);
if (!sp) {
cleanup_server(test);
return -1;
}
if (s > test->max_fd) test->max_fd = s;
if (test->on_new_stream)
test->on_new_stream(sp);
flag = -1;
}
}
FD_CLR(test->prot_listener, &read_set);
}
if (rec_streams_accepted == streams_to_rec && send_streams_accepted == streams_to_send) {
if (test->protocol->id != Ptcp) {
FD_CLR(test->prot_listener, &test->read_set);
close(test->prot_listener);
test->prot_listener = -1;
} else {
if (test->no_delay || test->settings->mss || test->settings->socket_bufsize) {
FD_CLR(test->listener, &test->read_set);
close(test->listener);
test->listener = -1;
if ((s = netannounce(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
cleanup_server(test);
i_errno = IELISTEN;
return -1;
}
test->listener = s;
FD_SET(test->listener, &test->read_set);
if (test->listener > test->max_fd) test->max_fd = test->listener;
}
}
test->prot_listener = -1;
/* Ensure that total requested data rate is not above limit */
iperf_size_t total_requested_rate = test->num_streams * test->settings->rate * (test->mode == BIDIRECTIONAL? 2 : 1);
if (test->settings->bitrate_limit > 0 && total_requested_rate > test->settings->bitrate_limit) {
if (iperf_get_verbose(test))
iperf_err(test, "Client total requested throughput rate of %" PRIu64 " bps exceeded %" PRIu64 " bps limit",
total_requested_rate, test->settings->bitrate_limit);
cleanup_server(test);
i_errno = IETOTALRATE;
return -1;
}
// Begin calculating CPU utilization
cpu_util(NULL);
if (iperf_set_send_state(test, TEST_START) != 0) {
cleanup_server(test);
return -1;
}
if (iperf_init_test(test) < 0) {
cleanup_server(test);
return -1;
}
if (create_server_timers(test) < 0) {
cleanup_server(test);
return -1;
}
if (create_server_omit_timer(test) < 0) {
cleanup_server(test);
return -1;
}
if (test->mode != RECEIVER)
if (iperf_create_send_timers(test) < 0) {
cleanup_server(test);
return -1;
}
if (iperf_set_send_state(test, TEST_RUNNING) != 0) {
cleanup_server(test);
return -1;
}
/* Create and spin up threads */
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) {
i_errno = IEPTHREADATTRINIT;
cleanup_server(test);
};
SLIST_FOREACH(sp, &test->streams, streams) {
if (pthread_create(&(sp->thr), &attr, &iperf_server_worker_run, sp) != 0) {
i_errno = IEPTHREADCREATE;
cleanup_server(test);
return -1;
}
sp->thread_created = 1;
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "Thread FD %d created\n", sp->socket);
}
}
if (test->debug_level >= DEBUG_LEVEL_INFO) {
iperf_printf(test, "All threads created\n");
}
if (pthread_attr_destroy(&attr) != 0) {
i_errno = IEPTHREADATTRDESTROY;
cleanup_server(test);
};
}
}
}
if (result == 0 ||
(timeout != NULL && timeout->tv_sec == 0 && timeout->tv_usec == 0)) {
/* Run the timers. */
iperf_time_now(&now);
tmr_run(&now);
}
}
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
}
iflush(test);
cleanup_server(test);
if (test->server_affinity != -1)
if (iperf_clearaffinity(test) != 0)
return -1;
return 0;
}
+613
View File
@@ -0,0 +1,613 @@
/*
* iperf, Copyright (c) 2014-2022, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/select.h>
#include <limits.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_tcp.h"
#include "iperf_util.h"
#include "net.h"
#include "cjson.h"
#if defined(HAVE_FLOWLABEL)
#include "flowlabel.h"
#endif /* HAVE_FLOWLABEL */
/* iperf_tcp_recv
*
* receives the data for TCP
*/
int
iperf_tcp_recv(struct iperf_stream *sp)
{
int r;
int sock_opt;
#if defined(HAVE_MSG_TRUNC)
sock_opt = sp->test->settings->skip_rx_copy ? MSG_TRUNC : 0;
#else
sock_opt = 0;
#endif /* HAVE_MSG_TRUNC */
r = Nrecv_no_select(sp->socket, sp->buffer, sp->settings->blksize, Ptcp, sock_opt);
if (r < 0)
return r;
/* Only count bytes received while we're in the correct state. */
if (sp->test->state == TEST_RUNNING) {
sp->result->bytes_received += r;
sp->result->bytes_received_this_interval += r;
}
else {
if (sp->test->debug)
printf("Late receive, state = %d-%s\n", sp->test->state, state_to_text(sp->test->state));
}
return r;
}
/* iperf_tcp_send
*
* sends the data for TCP
*/
int
iperf_tcp_send(struct iperf_stream *sp)
{
int r;
if (!sp->pending_size)
sp->pending_size = sp->settings->blksize;
if (sp->test->zerocopy)
r = Nsendfile(sp->buffer_fd, sp->socket, sp->buffer, sp->pending_size);
else
r = Nwrite(sp->socket, sp->buffer, sp->pending_size, Ptcp);
if (r < 0)
return r;
sp->pending_size -= r;
sp->result->bytes_sent += r;
sp->result->bytes_sent_this_interval += r;
if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG)
printf("sent %d bytes of %d, pending %d, total %" PRIu64 "\n",
r, sp->settings->blksize, sp->pending_size, sp->result->bytes_sent);
return r;
}
/* iperf_tcp_accept
*
* accept a new TCP stream connection
*/
int
iperf_tcp_accept(struct iperf_test * test)
{
int s;
signed char rbuf = ACCESS_DENIED;
char cookie[COOKIE_SIZE] = {0};
socklen_t len;
struct sockaddr_storage addr;
len = sizeof(addr);
if ((s = accept(test->listener, (struct sockaddr *) &addr, &len)) < 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
#if defined(HAVE_SO_MAX_PACING_RATE)
/* If fq socket pacing is specified, enable it. */
if (test->settings->fqrate) {
/* Convert bits per second to bytes per second */
uint64_t fqrate = test->settings->fqrate / 8;
if (fqrate > 0) {
if (test->debug) {
printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
}
if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
warning("Unable to set socket pacing");
}
}
}
#endif /* HAVE_SO_MAX_PACING_RATE */
if (Nread(s, cookie, COOKIE_SIZE, Ptcp) < 0) {
i_errno = IERECVCOOKIE;
close(s);
return -1;
}
if (strncmp(test->cookie, cookie, COOKIE_SIZE) != 0) {
if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Ptcp) < 0) {
iperf_err(test, "failed to send access denied from busy server to new connecting client, errno = %d\n", errno);
}
close(s);
}
return s;
}
/* iperf_tcp_listen
*
* start up a listener for TCP stream connections
*/
int
iperf_tcp_listen(struct iperf_test *test)
{
int s, opt;
socklen_t optlen;
int saved_errno;
int rcvbuf_actual, sndbuf_actual;
s = test->listener;
/*
* If certain parameters are specified (such as socket buffer
* size), then throw away the listening socket (the one for which
* we just accepted the control connection) and recreate it with
* those parameters. That way, when new data connections are
* set, they'll have all the correct parameters in place.
*
* It's not clear whether this is a requirement or a convenience.
*/
if (test->no_delay || test->mptcp || test->settings->mss || test->settings->socket_bufsize) {
struct addrinfo hints, *res;
char portstr[6];
int proto = 0;
FD_CLR(s, &test->read_set);
close(s);
snprintf(portstr, 6, "%d", test->server_port);
memset(&hints, 0, sizeof(hints));
/*
* If binding to the wildcard address with no explicit address
* family specified, then force us to get an AF_INET6 socket.
* More details in the comments in netanounce().
*/
if (test->settings->domain == AF_UNSPEC && !test->bind_address) {
hints.ai_family = AF_INET6;
}
else {
hints.ai_family = test->settings->domain;
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((gerror = getaddrinfo(test->bind_address, portstr, &hints, &res)) != 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
#if defined(HAVE_IPPROTO_MPTCP)
if (test->mptcp)
proto = IPPROTO_MPTCP;
#endif
if ((s = socket(res->ai_family, SOCK_STREAM, proto)) < 0) {
freeaddrinfo(res);
i_errno = IESTREAMLISTEN;
return -1;
}
if (test->no_delay) {
opt = 1;
if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETNODELAY;
return -1;
}
}
// XXX: Setting MSS is very buggy!
if ((opt = test->settings->mss)) {
if (setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETMSS;
return -1;
}
}
if ((opt = test->settings->socket_bufsize)) {
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
}
{
unsigned int rate = test->settings->rate / 8;
if (rate > 0) {
if (test->debug) {
printf("Setting application pacing to %u\n", rate);
}
}
}
opt = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IEREUSEADDR;
return -1;
}
/*
* If we got an IPv6 socket, figure out if it should accept IPv4
* connections as well. See documentation in netannounce() for
* more details.
*/
#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
if (res->ai_family == AF_INET6 && (test->settings->domain == AF_UNSPEC || test->settings->domain == AF_INET)) {
if (test->settings->domain == AF_UNSPEC)
opt = 0;
else
opt = 1;
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *) &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IEV6ONLY;
return -1;
}
}
#endif /* IPV6_V6ONLY */
if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
i_errno = IESTREAMLISTEN;
return -1;
}
freeaddrinfo(res);
if (listen(s, INT_MAX) < 0) {
i_errno = IESTREAMLISTEN;
close(s);
return -1;
}
test->listener = s;
}
/* Read back and verify the sender socket buffer size */
optlen = sizeof(sndbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
i_errno = IESETBUF2;
close(s);
return -1;
}
/* Read back and verify the receiver socket buffer size */
optlen = sizeof(rcvbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
i_errno = IESETBUF2;
close(s);
return -1;
}
if (test->json_output) {
cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
}
return s;
}
/* iperf_tcp_connect
*
* connect to a TCP stream listener
* This function is roughly similar to netdial(), and may indeed have
* been derived from it at some point, but it sets many TCP-specific
* options between socket creation and connection.
*/
int
iperf_tcp_connect(struct iperf_test *test)
{
struct addrinfo *server_res;
int s, opt;
socklen_t optlen;
int saved_errno;
int rcvbuf_actual, sndbuf_actual;
int proto = 0;
#if defined(HAVE_IPPROTO_MPTCP)
if (test->mptcp)
proto = IPPROTO_MPTCP;
#endif
s = create_socket(test->settings->domain, SOCK_STREAM, proto, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, &server_res);
if (s < 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
/* Set socket options */
if (test->no_delay) {
opt = 1;
if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETNODELAY;
return -1;
}
}
if ((opt = test->settings->mss)) {
if (setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETMSS;
return -1;
}
}
if ((opt = test->settings->socket_bufsize)) {
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
}
#if defined(HAVE_TCP_USER_TIMEOUT)
if ((opt = test->settings->snd_timeout)) {
if (setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETUSERTIMEOUT;
return -1;
}
}
#endif /* HAVE_TCP_USER_TIMEOUT */
/* Read back and verify the sender socket buffer size */
optlen = sizeof(sndbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
close(s);
freeaddrinfo(server_res);
i_errno = IESETBUF2;
return -1;
}
/* Read back and verify the receiver socket buffer size */
optlen = sizeof(rcvbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
close(s);
freeaddrinfo(server_res);
i_errno = IESETBUF2;
return -1;
}
if (test->json_output) {
cJSON *sock_bufsize_item = cJSON_GetObjectItem(test->json_start, "sock_bufsize");
if (sock_bufsize_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
}
cJSON *sndbuf_actual_item = cJSON_GetObjectItem(test->json_start, "sndbuf_actual");
if (sndbuf_actual_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
}
cJSON *rcvbuf_actual_item = cJSON_GetObjectItem(test->json_start, "rcvbuf_actual");
if (rcvbuf_actual_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
}
}
#if defined(HAVE_FLOWLABEL)
if (test->settings->flowlabel) {
if (server_res->ai_addr->sa_family != AF_INET6) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETFLOW;
return -1;
} else {
struct sockaddr_in6* sa6P = (struct sockaddr_in6*) server_res->ai_addr;
char freq_buf[sizeof(struct in6_flowlabel_req)];
struct in6_flowlabel_req *freq = (struct in6_flowlabel_req *)freq_buf;
int freq_len = sizeof(*freq);
memset(freq, 0, sizeof(*freq));
freq->flr_label = htonl(test->settings->flowlabel & IPV6_FLOWINFO_FLOWLABEL);
freq->flr_action = IPV6_FL_A_GET;
freq->flr_flags = IPV6_FL_F_CREATE;
freq->flr_share = IPV6_FL_S_ANY;
memcpy(&freq->flr_dst, &sa6P->sin6_addr, 16);
if (setsockopt(s, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, freq, freq_len) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETFLOW;
return -1;
}
sa6P->sin6_flowinfo = freq->flr_label;
opt = 1;
if (setsockopt(s, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESETFLOW;
return -1;
}
}
}
#endif /* HAVE_FLOWLABEL */
#if defined(HAVE_SO_MAX_PACING_RATE)
/* If socket pacing is specified try to enable it. */
if (test->settings->fqrate) {
/* Convert bits per second to bytes per second */
uint64_t fqrate = test->settings->fqrate / 8;
if (fqrate > 0) {
if (test->debug) {
printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
}
if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
warning("Unable to set socket pacing");
}
}
}
#endif /* HAVE_SO_MAX_PACING_RATE */
{
unsigned int rate = test->settings->rate / 8;
if (rate > 0) {
if (test->debug) {
printf("Setting application pacing to %u\n", rate);
}
}
}
/* Set common socket options */
iperf_common_sockopts(test, s);
if (connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen) < 0 && errno != EINPROGRESS) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
i_errno = IESTREAMCONNECT;
return -1;
}
freeaddrinfo(server_res);
/* Send cookie for verification */
if (Nwrite(s, test->cookie, COOKIE_SIZE, Ptcp) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
i_errno = IESENDCOOKIE;
return -1;
}
return s;
}
@@ -0,0 +1,63 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef IPERF_TCP_H
#define IPERF_TCP_H
/**
* iperf_tcp_accept -- accepts a new TCP connection
* on tcp_listener_socket for TCP data and param/result
* exchange messages
*returns 0 on success
*
*/
int iperf_tcp_accept(struct iperf_test *);
/**
* iperf_tcp_recv -- receives the data for TCP
* and the Param/result message exchange
*returns state of packet received
*
*/
int iperf_tcp_recv(struct iperf_stream *);
/**
* iperf_tcp_send -- sends the client data for TCP
* and the Param/result message exchanges
* returns: bytes sent
*
*/
int iperf_tcp_send(struct iperf_stream *) /* __attribute__((hot)) */;
int iperf_tcp_listen(struct iperf_test *);
int iperf_tcp_connect(struct iperf_test *);
#endif
+155
View File
@@ -0,0 +1,155 @@
/*
* iperf, Copyright (c) 2014-2018, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <stddef.h>
#include "iperf_config.h"
#include "iperf_time.h"
#ifdef HAVE_CLOCK_GETTIME
#include <time.h>
int
iperf_time_now(struct iperf_time *time1)
{
struct timespec ts;
int result;
result = clock_gettime(CLOCK_MONOTONIC, &ts);
if (result == 0) {
time1->secs = (uint32_t) ts.tv_sec;
time1->usecs = (uint32_t) ts.tv_nsec / 1000;
}
return result;
}
#else
#include <sys/time.h>
int
iperf_time_now(struct iperf_time *time1)
{
struct timeval tv;
int result;
result = gettimeofday(&tv, NULL);
time1->secs = tv.tv_sec;
time1->usecs = tv.tv_usec;
return result;
}
#endif
/* iperf_time_add_usecs
*
* Add a number of microseconds to a iperf_time.
*/
void
iperf_time_add_usecs(struct iperf_time *time1, uint64_t usecs)
{
uint64_t total_usecs;
total_usecs = time1->usecs + usecs;
time1->secs += total_usecs / 1000000L;
time1->usecs = total_usecs % 1000000L;
}
uint64_t
iperf_time_in_usecs(struct iperf_time *time)
{
return time->secs * 1000000LL + time->usecs;
}
double
iperf_time_in_secs(struct iperf_time *time)
{
return time->secs + time->usecs / 1000000.0;
}
/* iperf_time_compare
*
* Compare two timestamps
*
* Returns -1 if time1 is earlier, 1 if time1 is later,
* or 0 if the timestamps are equal.
*/
int
iperf_time_compare(struct iperf_time *time1, struct iperf_time *time2)
{
if (time1->secs < time2->secs)
return -1;
if (time1->secs > time2->secs)
return 1;
if (time1->usecs < time2->usecs)
return -1;
if (time1->usecs > time2->usecs)
return 1;
return 0;
}
/* iperf_time_diff
*
* Calculates the time from time2 to time1, assuming time1 is later than time2.
* The diff will always be positive, so the return value should be checked
* to determine if time1 was earlier than time2.
*
* Returns 1 if the time1 is less than or equal to time2, otherwise 0.
*/
int
iperf_time_diff(struct iperf_time *time1, struct iperf_time *time2, struct iperf_time *diff)
{
int past = 0;
int cmp = 0;
cmp = iperf_time_compare(time1, time2);
if (cmp == 0) {
diff->secs = 0;
diff->usecs = 0;
past = 1;
}
else if (cmp == 1) {
diff->secs = time1->secs - time2->secs;
diff->usecs = time1->usecs;
if (diff->usecs < time2->usecs) {
diff->secs -= 1;
diff->usecs += 1000000;
}
diff->usecs = diff->usecs - time2->usecs;
} else {
diff->secs = time2->secs - time1->secs;
diff->usecs = time2->usecs;
if (diff->usecs < time1->usecs) {
diff->secs -= 1;
diff->usecs += 1000000;
}
diff->usecs = diff->usecs - time1->usecs;
past = 1;
}
return past;
}
@@ -0,0 +1,49 @@
/*
* iperf, Copyright (c) 2014-2018, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __IPERF_TIME_H
#define __IPERF_TIME_H
#include <stdint.h>
struct iperf_time {
uint32_t secs;
uint32_t usecs;
};
int iperf_time_now(struct iperf_time *time1);
void iperf_time_add_usecs(struct iperf_time *time1, uint64_t usecs);
int iperf_time_compare(struct iperf_time *time1, struct iperf_time *time2);
int iperf_time_diff(struct iperf_time *time1, struct iperf_time *time2, struct iperf_time *diff);
uint64_t iperf_time_in_usecs(struct iperf_time *time);
double iperf_time_in_secs(struct iperf_time *time);
#endif
+634
View File
@@ -0,0 +1,634 @@
/*
* iperf, Copyright (c) 2014-2022, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/select.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_util.h"
#include "iperf_udp.h"
#include "timer.h"
#include "net.h"
#include "cjson.h"
/* iperf_udp_recv
*
* receives the data for UDP
*/
int
iperf_udp_recv(struct iperf_stream *sp)
{
uint32_t sec, usec;
uint64_t pcount;
int r;
int size = sp->settings->blksize;
int first_packet = 0;
double transit = 0, d = 0;
struct iperf_time sent_time, arrival_time, temp_time;
struct iperf_test *test = sp->test;
int sock_opt = 0;
#if defined(HAVE_MSG_TRUNC)
// UDP recv() with MSG_TRUNC reads only the size bytes, but return the length of the full packet
if (sp->test->settings->skip_rx_copy) {
sock_opt = MSG_TRUNC;
size = sizeof(sec) + sizeof(usec) + sizeof(pcount);
}
#endif /* HAVE_MSG_TRUNC */
r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt);
/*
* If we got an error in the read, or if we didn't read anything
* because the underlying read(2) got a EAGAIN, then skip packet
* processing.
*/
if (r <= 0)
return r;
/* Only count bytes received while we're in the correct state. */
if (test->state == TEST_RUNNING) {
/*
* For jitter computation below, it's important to know if this
* packet is the first packet received.
*/
if (sp->result->bytes_received == 0) {
first_packet = 1;
}
sp->result->bytes_received += r;
sp->result->bytes_received_this_interval += r;
/* Dig the various counters out of the incoming UDP packet */
if (test->udp_counters_64bit) {
memcpy(&sec, sp->buffer, sizeof(sec));
memcpy(&usec, sp->buffer+4, sizeof(usec));
memcpy(&pcount, sp->buffer+8, sizeof(pcount));
sec = ntohl(sec);
usec = ntohl(usec);
pcount = be64toh(pcount);
sent_time.secs = sec;
sent_time.usecs = usec;
}
else {
uint32_t pc;
memcpy(&sec, sp->buffer, sizeof(sec));
memcpy(&usec, sp->buffer+4, sizeof(usec));
memcpy(&pc, sp->buffer+8, sizeof(pc));
sec = ntohl(sec);
usec = ntohl(usec);
pcount = ntohl(pc);
sent_time.secs = sec;
sent_time.usecs = usec;
}
if (test->debug_level >= DEBUG_LEVEL_DEBUG)
fprintf(stderr, "pcount %" PRIu64 " packet_count %" PRIu64 "\n", pcount, sp->packet_count);
/*
* Try to handle out of order packets. The way we do this
* uses a constant amount of storage but might not be
* correct in all cases. In particular we seem to have the
* assumption that packets can't be duplicated in the network,
* because duplicate packets will possibly cause some problems here.
*
* First figure out if the sequence numbers are going forward.
* Note that pcount is the sequence number read from the packet,
* and sp->packet_count is the highest sequence number seen so
* far (so we're expecting to see the packet with sequence number
* sp->packet_count + 1 arrive next).
*/
if (pcount >= sp->packet_count + 1) {
/* Forward, but is there a gap in sequence numbers? */
if (pcount > sp->packet_count + 1) {
/* There's a gap so count that as a loss. */
sp->cnt_error += (pcount - 1) - sp->packet_count;
if (test->debug_level >= DEBUG_LEVEL_INFO)
fprintf(stderr, "LOST %" PRIu64 " PACKETS - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", (pcount - sp->packet_count + 1), pcount, sp->packet_count + 1, sp->socket);
}
/* Update the highest sequence number seen so far. */
sp->packet_count = pcount;
} else {
/*
* Sequence number went backward (or was stationary?!?).
* This counts as an out-of-order packet.
*/
sp->outoforder_packets++;
/*
* If we have lost packets, then the fact that we are now
* seeing an out-of-order packet offsets a prior sequence
* number gap that was counted as a loss. So we can take
* away a loss.
*/
if (sp->cnt_error > 0)
sp->cnt_error--;
/* Log the out-of-order packet */
if (test->debug_level >= DEBUG_LEVEL_INFO)
fprintf(stderr, "OUT OF ORDER - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", pcount, sp->packet_count + 1, sp->socket);
}
/*
* jitter measurement
*
* This computation is based on RFC 1889 (specifically
* sections 6.3.1 and A.8).
*
* Note that synchronized clocks are not required since
* the source packet delta times are known. Also this
* computation does not require knowing the round-trip
* time.
*/
iperf_time_now(&arrival_time);
iperf_time_diff(&arrival_time, &sent_time, &temp_time);
transit = iperf_time_in_secs(&temp_time);
/* Hack to handle the first packet by initializing prev_transit. */
if (first_packet)
sp->prev_transit = transit;
d = transit - sp->prev_transit;
if (d < 0)
d = -d;
sp->prev_transit = transit;
sp->jitter += (d - sp->jitter) / 16.0;
}
else {
if (test->debug_level >= DEBUG_LEVEL_INFO)
printf("Late receive, state = %d\n", test->state);
}
return r;
}
/* iperf_udp_send
*
* sends the data for UDP
*/
int
iperf_udp_send(struct iperf_stream *sp)
{
int r;
int size = sp->settings->blksize;
struct iperf_time before;
iperf_time_now(&before);
++sp->packet_count;
if (sp->test->udp_counters_64bit) {
uint32_t sec, usec;
uint64_t pcount;
sec = htonl(before.secs);
usec = htonl(before.usecs);
pcount = htobe64(sp->packet_count);
memcpy(sp->buffer, &sec, sizeof(sec));
memcpy(sp->buffer+4, &usec, sizeof(usec));
memcpy(sp->buffer+8, &pcount, sizeof(pcount));
}
else {
uint32_t sec, usec, pcount;
sec = htonl(before.secs);
usec = htonl(before.usecs);
pcount = htonl(sp->packet_count);
memcpy(sp->buffer, &sec, sizeof(sec));
memcpy(sp->buffer+4, &usec, sizeof(usec));
memcpy(sp->buffer+8, &pcount, sizeof(pcount));
}
r = Nwrite(sp->socket, sp->buffer, size, Pudp);
if (r <= 0) {
--sp->packet_count; /* Don't count messages that no data was sent from them.
* Allows "resending" a massage with the same numbering */
if (r < 0) {
if (r == NET_SOFTERROR && sp->test->debug_level >= DEBUG_LEVEL_INFO)
printf("UDP send failed on NET_SOFTERROR. errno=%s\n", strerror(errno));
return r;
}
}
sp->result->bytes_sent += r;
sp->result->bytes_sent_this_interval += r;
if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG)
printf("sent %d bytes of %d, total %" PRIu64 "\n", r, sp->settings->blksize, sp->result->bytes_sent);
return r;
}
/**************************************************************************/
/*
* The following functions all have to do with managing UDP data sockets.
* UDP of course is connectionless, so there isn't really a concept of
* setting up a connection, although connect(2) can (and is) used to
* bind the remote end of sockets. We need to simulate some of the
* connection management that is built-in to TCP so that each side of the
* connection knows about each other before the real data transfers begin.
*/
/*
* Set and verify socket buffer sizes.
* Return 0 if no error, -1 if an error, +1 if socket buffers are
* potentially too small to hold a message.
*/
int
iperf_udp_buffercheck(struct iperf_test *test, int s)
{
int rc = 0;
int sndbuf_actual, rcvbuf_actual;
/*
* Set socket buffer size if requested. Do this for both sending and
* receiving so that we can cover both normal and --reverse operation.
*/
int opt;
socklen_t optlen;
if ((opt = test->settings->socket_bufsize)) {
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
i_errno = IESETBUF;
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
i_errno = IESETBUF;
return -1;
}
}
/* Read back and verify the sender socket buffer size */
optlen = sizeof(sndbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
i_errno = IESETBUF2;
return -1;
}
if (test->settings->blksize > sndbuf_actual) {
char str[WARN_STR_LEN];
snprintf(str, sizeof(str),
"Block size %d > sending socket buffer size %d",
test->settings->blksize, sndbuf_actual);
warning(str);
rc = 1;
}
/* Read back and verify the receiver socket buffer size */
optlen = sizeof(rcvbuf_actual);
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
i_errno = IESETBUF;
return -1;
}
if (test->debug) {
printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
}
if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
i_errno = IESETBUF2;
return -1;
}
if (test->settings->blksize > rcvbuf_actual) {
char str[WARN_STR_LEN];
snprintf(str, sizeof(str),
"Block size %d > receiving socket buffer size %d",
test->settings->blksize, rcvbuf_actual);
warning(str);
rc = 1;
}
if (test->json_output) {
cJSON *sock_bufsize_item = cJSON_GetObjectItem(test->json_start, "sock_bufsize");
if (sock_bufsize_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
}
cJSON *sndbuf_actual_item = cJSON_GetObjectItem(test->json_start, "sndbuf_actual");
if (sndbuf_actual_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
}
cJSON *rcvbuf_actual_item = cJSON_GetObjectItem(test->json_start, "rcvbuf_actual");
if (rcvbuf_actual_item == NULL) {
cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
}
}
return rc;
}
/*
* iperf_udp_accept
*
* Accepts a new UDP "connection"
*/
int
iperf_udp_accept(struct iperf_test *test)
{
struct sockaddr_storage sa_peer;
unsigned int buf;
socklen_t len;
int sz, s;
int rc;
/*
* Get the current outstanding socket. This socket will be used to handle
* data transfers and a new "listening" socket will be created.
*/
s = test->prot_listener;
/*
* Grab the UDP packet sent by the client. From that we can extract the
* client's address, and then use that information to bind the remote side
* of the socket to the client.
*/
len = sizeof(sa_peer);
if ((sz = recvfrom(test->prot_listener, &buf, sizeof(buf), 0, (struct sockaddr *) &sa_peer, &len)) < 0) {
i_errno = IESTREAMACCEPT;
return -1;
}
if (connect(s, (struct sockaddr *) &sa_peer, len) < 0) {
i_errno = IESTREAMACCEPT;
return -1;
}
/* Check and set socket buffer sizes */
rc = iperf_udp_buffercheck(test, s);
if (rc < 0)
/* error */
return rc;
/*
* If the socket buffer was too small, but it was the default
* size, then try explicitly setting it to something larger.
*/
if (rc > 0) {
if (test->settings->socket_bufsize == 0) {
char str[WARN_STR_LEN];
int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA;
snprintf(str, sizeof(str), "Increasing socket buffer size to %d",
bufsize);
warning(str);
test->settings->socket_bufsize = bufsize;
rc = iperf_udp_buffercheck(test, s);
if (rc < 0)
return rc;
}
}
#if defined(HAVE_SO_MAX_PACING_RATE)
/* If socket pacing is specified, try it. */
if (test->settings->fqrate) {
/* Convert bits per second to bytes per second */
uint64_t fqrate = test->settings->fqrate / 8;
if (fqrate > 0) {
if (test->debug) {
printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
}
if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
warning("Unable to set socket pacing");
}
}
}
#endif /* HAVE_SO_MAX_PACING_RATE */
{
unsigned int rate = test->settings->rate / 8;
if (rate > 0) {
if (test->debug) {
printf("Setting application pacing to %u\n", rate);
}
}
}
/*
* Create a new "listening" socket to replace the one we were using before.
*/
FD_CLR(test->prot_listener, &test->read_set); // No control messages from old listener
test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port);
if (test->prot_listener < 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
FD_SET(test->prot_listener, &test->read_set);
test->max_fd = (test->max_fd < test->prot_listener) ? test->prot_listener : test->max_fd;
/* Let the client know we're ready "accept" another UDP "stream" */
buf = UDP_CONNECT_REPLY;
if (write(s, &buf, sizeof(buf)) < 0) {
i_errno = IESTREAMWRITE;
return -1;
}
return s;
}
/*
* iperf_udp_listen
*
* Start up a listener for UDP stream connections. Unlike for TCP,
* there is no listen(2) for UDP. This socket will however accept
* a UDP datagram from a client (indicating the client's presence).
*/
int
iperf_udp_listen(struct iperf_test *test)
{
int s;
if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
/*
* The caller will put this value into test->prot_listener.
*/
return s;
}
/*
* iperf_udp_connect
*
* "Connect" to a UDP stream listener.
*/
int
iperf_udp_connect(struct iperf_test *test)
{
int s, sz;
unsigned int buf;
#ifdef SO_RCVTIMEO
struct timeval tv;
#endif
int rc;
int i, max_len_wait_for_reply;
/* Create and bind our local socket. */
if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
/* Check and set socket buffer sizes */
rc = iperf_udp_buffercheck(test, s);
if (rc < 0)
/* error */
return rc;
/*
* If the socket buffer was too small, but it was the default
* size, then try explicitly setting it to something larger.
*/
if (rc > 0) {
if (test->settings->socket_bufsize == 0) {
char str[WARN_STR_LEN];
int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA;
snprintf(str, sizeof(str), "Increasing socket buffer size to %d",
bufsize);
warning(str);
test->settings->socket_bufsize = bufsize;
rc = iperf_udp_buffercheck(test, s);
if (rc < 0)
return rc;
}
}
#if defined(HAVE_SO_MAX_PACING_RATE)
/* If socket pacing is available and not disabled, try it. */
if (test->settings->fqrate) {
/* Convert bits per second to bytes per second */
uint64_t fqrate = test->settings->fqrate / 8;
if (fqrate > 0) {
if (test->debug) {
printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
}
if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
warning("Unable to set socket pacing");
}
}
}
#endif /* HAVE_SO_MAX_PACING_RATE */
{
unsigned int rate = test->settings->rate / 8;
if (rate > 0) {
if (test->debug) {
printf("Setting application pacing to %u\n", rate);
}
}
}
/* Set common socket options */
iperf_common_sockopts(test, s);
#ifdef SO_RCVTIMEO
/* 30 sec timeout for a case when there is a network problem. */
tv.tv_sec = 30;
tv.tv_usec = 0;
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
#endif
/*
* Write a datagram to the UDP stream to let the server know we're here.
* The server learns our address by obtaining its peer's address.
*/
buf = UDP_CONNECT_MSG;
if (test->debug) {
printf("Sending Connect message to Socket %d\n", s);
}
if (write(s, &buf, sizeof(buf)) < 0) {
// XXX: Should this be changed to IESTREAMCONNECT?
i_errno = IESTREAMWRITE;
return -1;
}
/*
* Wait until the server replies back to us with the "accept" response.
*/
i = 0;
max_len_wait_for_reply = sizeof(buf);
if (test->reverse) /* In reverse mode allow few packets to have the "accept" response - to handle out of order packets */
max_len_wait_for_reply += MAX_REVERSE_OUT_OF_ORDER_PACKETS * test->settings->blksize;
do {
if ((sz = recv(s, &buf, sizeof(buf), 0)) < 0) {
i_errno = IESTREAMREAD;
return -1;
}
if (test->debug) {
printf("Connect received for Socket %d, sz=%d, buf=%x, i=%d, max_len_wait_for_reply=%d\n", s, sz, buf, i, max_len_wait_for_reply);
}
i += sz;
} while (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY && i < max_len_wait_for_reply);
if (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY) {
i_errno = IESTREAMREAD;
return -1;
}
return s;
}
/* iperf_udp_init
*
* initializer for UDP streams in TEST_START
*/
int
iperf_udp_init(struct iperf_test *test)
{
return 0;
}
@@ -0,0 +1,64 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __IPERF_UDP_H
#define __IPERF_UDP_H
/**
* iperf_udp_recv -- receives the client data for UDP
*
*returns state of packet received
*
*/
int iperf_udp_recv(struct iperf_stream *);
/**
* iperf_udp_send -- sends the client data for UDP
*
* returns: bytes sent
*
*/
int iperf_udp_send(struct iperf_stream *) /* __attribute__((hot)) */;
/**
* iperf_udp_accept -- accepts a new UDP connection
* on udp_listener_socket
*returns 0 on success
*
*/
int iperf_udp_accept(struct iperf_test *);
int iperf_udp_listen(struct iperf_test *);
int iperf_udp_connect(struct iperf_test *);
int iperf_udp_init(struct iperf_test *);
#endif
+660
View File
@@ -0,0 +1,660 @@
/*
* iperf, Copyright (c) 2014, 2016, 2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
/* iperf_util.c
*
* Iperf utility functions
*
*/
#include "iperf_config.h"
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/utsname.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include "cjson.h"
#include "iperf.h"
#include "iperf_api.h"
/*
* Read entropy from /dev/urandom
* Errors are fatal.
* Returns 0 on success.
*/
int readentropy(void *out, size_t outsize)
{
static FILE *frandom;
static const char rndfile[] = "/dev/urandom";
if (!outsize) return 0;
if (frandom == NULL) {
frandom = fopen(rndfile, "rb");
if (frandom == NULL) {
iperf_errexit(NULL, "error - failed to open %s: %s\n",
rndfile, strerror(errno));
}
setbuf(frandom, NULL);
}
if (fread(out, 1, outsize, frandom) != outsize) {
iperf_errexit(NULL, "error - failed to read %s: %s\n",
rndfile,
feof(frandom) ? "EOF" : strerror(errno));
}
return 0;
}
/*
* Fills buffer with repeating pattern (similar to pattern that used in iperf2)
*/
void fill_with_repeating_pattern(void *out, size_t outsize)
{
size_t i;
int counter = 0;
char *buf = (char *)out;
if (!outsize) return;
for (i = 0; i < outsize; i++) {
buf[i] = (char)('0' + counter);
if (counter >= 9)
counter = 0;
else
counter++;
}
}
/* make_cookie
*
* Generate and return a cookie string
*
* Iperf uses this function to create test "cookies" which
* serve as unique test identifiers. These cookies are also
* used for the authentication of stream connections.
* Assumes cookie has size (COOKIE_SIZE + 1) char's.
*/
void
make_cookie(const char *cookie)
{
unsigned char *out = (unsigned char*)cookie;
size_t pos;
static const unsigned char rndchars[] = "abcdefghijklmnopqrstuvwxyz234567";
readentropy(out, COOKIE_SIZE);
for (pos = 0; pos < (COOKIE_SIZE - 1); pos++) {
out[pos] = rndchars[out[pos] % (sizeof(rndchars) - 1)];
}
out[pos] = '\0';
}
/* is_closed
*
* Test if the file descriptor fd is closed.
*
* Iperf uses this function to test whether a TCP stream socket
* is closed, because accepting and denying an invalid connection
* in iperf_tcp_accept is not considered an error.
*/
int
is_closed(int fd)
{
struct timeval tv;
fd_set readset;
FD_ZERO(&readset);
FD_SET(fd, &readset);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(fd+1, &readset, NULL, NULL, &tv) < 0) {
if (errno == EBADF)
return 1;
}
return 0;
}
double
timeval_to_double(struct timeval * tv)
{
double d;
d = tv->tv_sec + tv->tv_usec / 1000000;
return d;
}
int
timeval_equals(struct timeval * tv0, struct timeval * tv1)
{
if ( tv0->tv_sec == tv1->tv_sec && tv0->tv_usec == tv1->tv_usec )
return 1;
else
return 0;
}
double
timeval_diff(struct timeval * tv0, struct timeval * tv1)
{
double time1, time2;
time1 = tv0->tv_sec + (tv0->tv_usec / 1000000.0);
time2 = tv1->tv_sec + (tv1->tv_usec / 1000000.0);
time1 = time1 - time2;
if (time1 < 0)
time1 = -time1;
return time1;
}
void
cpu_util(double pcpu[3])
{
static struct iperf_time last;
static clock_t clast;
static struct rusage rlast;
struct iperf_time now, temp_time;
clock_t ctemp;
struct rusage rtemp;
double timediff;
double userdiff;
double systemdiff;
if (pcpu == NULL) {
iperf_time_now(&last);
clast = clock();
getrusage(RUSAGE_SELF, &rlast);
return;
}
iperf_time_now(&now);
ctemp = clock();
getrusage(RUSAGE_SELF, &rtemp);
iperf_time_diff(&now, &last, &temp_time);
timediff = iperf_time_in_usecs(&temp_time);
userdiff = ((rtemp.ru_utime.tv_sec * 1000000.0 + rtemp.ru_utime.tv_usec) -
(rlast.ru_utime.tv_sec * 1000000.0 + rlast.ru_utime.tv_usec));
systemdiff = ((rtemp.ru_stime.tv_sec * 1000000.0 + rtemp.ru_stime.tv_usec) -
(rlast.ru_stime.tv_sec * 1000000.0 + rlast.ru_stime.tv_usec));
pcpu[0] = (((ctemp - clast) * 1000000.0 / CLOCKS_PER_SEC) / timediff) * 100;
pcpu[1] = (userdiff / timediff) * 100;
pcpu[2] = (systemdiff / timediff) * 100;
}
const char *
get_system_info(void)
{
static char buf[1024];
struct utsname uts;
memset(buf, 0, 1024);
uname(&uts);
snprintf(buf, sizeof(buf), "%s %s %s %s %s", uts.sysname, uts.nodename,
uts.release, uts.version, uts.machine);
return buf;
}
const char *
get_optional_features(void)
{
static char features[1024];
unsigned int numfeatures = 0;
snprintf(features, sizeof(features), "Optional features available: ");
#if defined(HAVE_CPU_AFFINITY)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "CPU affinity setting",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_CPU_AFFINITY */
#if defined(HAVE_FLOWLABEL)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "IPv6 flow label",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_FLOWLABEL */
#if defined(HAVE_SCTP_H)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "SCTP",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_SCTP_H */
#if defined(HAVE_TCP_CONGESTION)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "TCP congestion algorithm setting",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_TCP_CONGESTION */
#if defined(HAVE_SENDFILE)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "sendfile / zerocopy",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_SENDFILE */
#if defined(HAVE_SO_MAX_PACING_RATE)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "socket pacing",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_SO_MAX_PACING_RATE */
#if defined(HAVE_SSL)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "authentication",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_SSL */
#if defined(HAVE_SO_BINDTODEVICE)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "bind to device",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_SO_BINDTODEVICE */
#if defined(HAVE_DONT_FRAGMENT)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "support IPv4 don't fragment",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_DONT_FRAGMENT */
#if defined(HAVE_PTHREAD)
if (numfeatures > 0) {
strncat(features, ", ",
sizeof(features) - strlen(features) - 1);
}
strncat(features, "POSIX threads",
sizeof(features) - strlen(features) - 1);
numfeatures++;
#endif /* HAVE_PTHREAD */
if (numfeatures == 0) {
strncat(features, "None",
sizeof(features) - strlen(features) - 1);
}
return features;
}
/* Helper routine for building cJSON objects in a printf-like manner.
**
** Sample call:
** j = iperf_json_printf("foo: %b bar: %d bletch: %f eep: %s", b, i, f, s);
**
** The four formatting characters and the types they expect are:
** %b boolean int
** %d integer int64_t
** %f floating point double
** %s string char *
** If the values you're passing in are not these exact types, you must
** cast them, there is no automatic type coercion/widening here.
**
** The colons mark the end of field names, and blanks are ignored.
**
** This routine is not particularly robust, but it's not part of the API,
** it's just for internal iperf3 use.
*/
cJSON*
iperf_json_printf(const char *format, ...)
{
cJSON* o;
va_list argp;
const char *cp;
char name[100];
char* np;
cJSON* j;
o = cJSON_CreateObject();
if (o == NULL)
return NULL;
va_start(argp, format);
np = name;
for (cp = format; *cp != '\0'; ++cp) {
switch (*cp) {
case ' ':
break;
case ':':
*np = '\0';
break;
case '%':
++cp;
switch (*cp) {
case 'b':
j = cJSON_CreateBool(va_arg(argp, int));
break;
case 'd':
j = cJSON_CreateNumber(va_arg(argp, int64_t));
break;
case 'f':
j = cJSON_CreateNumber(va_arg(argp, double));
break;
case 's':
j = cJSON_CreateString(va_arg(argp, char *));
break;
default:
va_end(argp);
return NULL;
}
if (j == NULL) {
va_end(argp);
return NULL;
}
cJSON_AddItemToObject(o, name, j);
np = name;
break;
default:
*np++ = *cp;
break;
}
}
va_end(argp);
return o;
}
/********************** cJSON GetObjectItem w/ Type Helper ********************/
cJSON * iperf_cJSON_GetObjectItemType(cJSON * j, char * item_string, int expected_type){
cJSON *j_p;
if((j_p = cJSON_GetObjectItem(j, item_string)) != NULL)
switch(expected_type){
case cJSON_True:
if(cJSON_IsBool(j_p))
return j_p;
else
iperf_err(NULL, "iperf_cJSON_GetObjectItemType mismatch %s", item_string);
break;
case cJSON_String:
if(cJSON_IsString(j_p))
return j_p;
else
iperf_err(NULL, "iperf_cJSON_GetObjectItemType mismatch %s", item_string);
break;
case cJSON_Number:
if(cJSON_IsNumber(j_p))
return j_p;
else
iperf_err(NULL, "iperf_cJSON_GetObjectItemType mismatch %s", item_string);
break;
case cJSON_Array:
if(cJSON_IsArray(j_p))
return j_p;
else
iperf_err(NULL, "iperf_cJSON_GetObjectItemType mismatch %s", item_string);
break;
default:
iperf_err(NULL, "unsupported type");
}
return NULL;
}
/* Debugging routine to dump out an fd_set. */
void
iperf_dump_fdset(FILE *fp, const char *str, int nfds, fd_set *fds)
{
int fd;
int comma;
fprintf(fp, "%s: [", str);
comma = 0;
for (fd = 0; fd < nfds; ++fd) {
if (FD_ISSET(fd, fds)) {
if (comma)
fprintf(fp, ", ");
fprintf(fp, "%d", fd);
comma = 1;
}
}
fprintf(fp, "]\n");
}
/*
* daemon(3) implementation for systems lacking one.
* Cobbled together from various daemon(3) implementations,
* not intended to be general-purpose. */
#ifndef HAVE_DAEMON
int daemon(int nochdir, int noclose)
{
pid_t pid = 0;
pid_t sid = 0;
int fd;
/*
* Ignore any possible SIGHUP when the parent process exits.
* Note that the iperf3 server process will eventually install
* its own signal handler for SIGHUP, so we can be a little
* sloppy about not restoring the prior value. This does not
* generalize.
*/
signal(SIGHUP, SIG_IGN);
pid = fork();
if (pid < 0) {
return -1;
}
if (pid > 0) {
/* Use _exit() to avoid doing atexit() stuff. */
_exit(0);
}
sid = setsid();
if (sid < 0) {
return -1;
}
/*
* Fork again to avoid becoming a session leader.
* This might only matter on old SVr4-derived OSs.
* Note in particular that glibc and FreeBSD libc
* only fork once.
*/
pid = fork();
if (pid == -1) {
return -1;
} else if (pid != 0) {
_exit(0);
}
if (!nochdir) {
chdir("/");
}
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > 2) {
close(fd);
}
}
return (0);
}
#endif /* HAVE_DAEMON */
/* Compatibility version of getline(3) for systems that don't have it.. */
#ifndef HAVE_GETLINE
/* The following code adopted from NetBSD's getline.c, which is: */
/*-
* Copyright (c) 2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Christos Zoulas.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
ssize_t
getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
{
char *ptr, *eptr;
if (*buf == NULL || *bufsiz == 0) {
*bufsiz = BUFSIZ;
if ((*buf = malloc(*bufsiz)) == NULL)
return -1;
}
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
int c = fgetc(fp);
if (c == -1) {
if (feof(fp)) {
ssize_t diff = (ssize_t)(ptr - *buf);
if (diff != 0) {
*ptr = '\0';
return diff;
}
}
return -1;
}
*ptr++ = c;
if (c == delimiter) {
*ptr = '\0';
return ptr - *buf;
}
if (ptr + 2 >= eptr) {
char *nbuf;
size_t nbufsiz = *bufsiz * 2;
ssize_t d = ptr - *buf;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
return -1;
*buf = nbuf;
*bufsiz = nbufsiz;
eptr = nbuf + nbufsiz;
ptr = nbuf + d;
}
}
}
ssize_t
getline(char **buf, size_t *bufsiz, FILE *fp)
{
return getdelim(buf, bufsiz, '\n', fp);
}
#endif
/* Translate numeric State to text - for debugging pupposes */
char *
state_to_text(signed char state)
{
char *txt;
switch (state) {
case 0: txt = "Test reset"; break;
case TEST_START: txt = "TEST_START - starting a new test"; break;
case TEST_RUNNING: txt = "TEST_RUNNING"; break;
case TEST_END: txt = "TEST_END"; break;
case PARAM_EXCHANGE: txt = "PARAM_EXCHANGE - Client to Server Parameters Exchange"; break;
case CREATE_STREAMS: txt = "CREATE_STREAMS"; break;
case SERVER_TERMINATE: txt = "SERVER_TERMINATE"; break;
case CLIENT_TERMINATE: txt = "CLIENT_TERMINATE"; break;
case EXCHANGE_RESULTS: txt = "EXCHANGE_RESULTS"; break;
case DISPLAY_RESULTS: txt = "DISPLAY_RESULTS"; break;
case IPERF_START: txt = "IPERF_START - waiting for a new test"; break;
case IPERF_DONE: txt = "IPERF_DONE"; break;
case ACCESS_DENIED: txt = "ACCESS_DENIED - Server is busy"; break;
case SERVER_ERROR: txt = "SERVER_ERROR"; break;
default: txt = "Unknown State";
}
return txt;
}
@@ -0,0 +1,70 @@
/*
* iperf, Copyright (c) 2014-2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __IPERF_UTIL_H
#define __IPERF_UTIL_H
#include "iperf_config.h"
#include "cjson.h"
#include <sys/select.h>
#include <stddef.h>
int readentropy(void *out, size_t outsize);
void fill_with_repeating_pattern(void *out, size_t outsize);
void make_cookie(char *);
int is_closed(int);
double timeval_to_double(struct timeval *tv);
int timeval_equals(struct timeval *tv0, struct timeval *tv1);
double timeval_diff(struct timeval *tv0, struct timeval *tv1);
void cpu_util(double pcpu[3]);
const char* get_system_info(void);
const char* get_optional_features(void);
cJSON* iperf_json_printf(const char *format, ...);
cJSON * iperf_cJSON_GetObjectItemType(cJSON * j_p, char * item_string, int expected_type);
void iperf_dump_fdset(FILE *fp, const char *str, int nfds, fd_set *fds);
#ifndef HAVE_DAEMON
extern int daemon(int nochdir, int noclose);
#endif /* HAVE_DAEMON */
#ifndef HAVE_GETLINE
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
#endif /* HAVE_GETLINE */
char * state_to_text(signed char state);
#endif
+115
View File
@@ -0,0 +1,115 @@
.TH LIBIPERF 3 "January 2022" ESnet "User Manuals"
.SH NAME
libiperf \- API for iperf3 network throughput tester
.SH SYNOPSIS
#include <iperf_api.h>
.br
\-liperf
.SH DESCRIPTION
.PP
Libiperf gives you access to all the functionality of the iperf3
network testing tool.
You can build it directly into your own program, instead of having
to run it as a shell command.
.SH CALLS
Initialization / termination:
.nf
struct iperf_test *iperf_new_test();
int iperf_defaults(struct iperf_test *t);
void iperf_free_test(struct iperf_test *t);
.fi
Setting test parameters:
.nf
void iperf_set_test_role( struct iperf_test *pt, char role );
void iperf_set_test_bind_address( struct iperf_test *t, char *bind_address );
void iperf_set_test_bind_dev( struct iperf_test *t, char *bind_dev );
void iperf_set_test_server_hostname( struct iperf_test *t, char *server_host );
void iperf_set_test_server_port( struct iperf_test *t, int server_port );
void iperf_set_test_duration( struct iperf_test *t, int duration );
void iperf_set_test_blksize( struct iperf_test *t, int blksize );
void iperf_set_test_num_streams( struct iperf_test *t, int num_streams );
void iperf_set_test_json_output( struct iperf_test *t, int json_output );
void iperf_set_test_json_stream( struct iperf_test *t, int json_stream );
int iperf_has_zerocopy( void );
void iperf_set_test_zerocopy( struct iperf_test* t, int zerocopy );
void iperf_set_test_tos( struct iperf_test* t, int tos );
.fi
Authentication functions:
.nf
void iperf_set_test_client_username(struct iperf_test *ipt, char *client_username)
void iperf_set_test_client_password(struct iperf_test *ipt, char *client_password)
void iperf_set_test_client_rsa_pubkey(struct iperf_test *ipt, char *client_rsa_pubkey_base64)
.fi
Running a test:
.nf
int iperf_run_client(struct iperf_test *);
int iperf_run_server(struct iperf_test *);
void iperf_reset_test(struct iperf_test *);
.fi
Output:
.nf
FILE *iperf_get_test_outfile(struct iperf_test *);
char* iperf_get_test_json_output_string(struct iperf_test *);
.fi
Error reporting:
.nf
void iperf_err(struct iperf_test *t, const char *format, ...);
char *iperf_strerror(int);
extern int i_errno;
.fi
This is not a complete list of the available calls.
See the include file for more.
.SH EXAMPLES
Here's some sample code that runs an iperf client:
.nf
struct iperf_test *test;
test = iperf_new_test();
if ( test == NULL ) {
fprintf( stderr, "%s: failed to create test\n", argv0 );
exit( EXIT_FAILURE );
}
iperf_defaults( test );
iperf_set_test_role( test, 'c' );
iperf_set_test_server_hostname( test, host );
iperf_set_test_server_port( test, port );
if ( iperf_run_client( test ) < 0 ) {
fprintf( stderr, "%s: error - %s\n", argv0, iperf_strerror( i_errno ) );
exit( EXIT_FAILURE );
}
iperf_free_test( test );
.fi
And here's a server:
.nf
struct iperf_test *test;
test = iperf_new_test();
if ( test == NULL ) {
fprintf( stderr, "%s: failed to create test\n", argv0 );
exit( EXIT_FAILURE );
}
iperf_defaults( test );
iperf_set_test_role( test, 's' );
iperf_set_test_server_port( test, port );
for (;;) {
if ( iperf_run_server( test ) < 0 )
fprintf( stderr, "%s: error - %s\n\n", argv0, iperf_strerror( i_errn
o ) );
iperf_reset_test( test );
}
iperf_free_test( test );
.fi
These are not complete programs, just excerpts.
The full runnable source code can be found in the examples subdirectory
of the iperf3 source tree.
.SH AUTHORS
A list of the contributors to iperf3 can be found within the
documentation located at
\fChttps://software.es.net/iperf/dev.html#authors\fR.
.SH "SEE ALSO"
iperf3(1),
https://software.es.net/iperf/
+214
View File
@@ -0,0 +1,214 @@
/*
* iperf, Copyright (c) 2014-2023, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include "iperf_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_util.h"
#include "iperf_locale.h"
#include "net.h"
#include "units.h"
static int run(struct iperf_test *test);
/**************************************************************************/
int
main(int argc, char **argv)
{
struct iperf_test *test;
/*
* Atomics check. We prefer to have atomic types (which is
* basically on any compiler supporting C11 or better). If we
* don't have them, we try to approximate the type we need with a
* regular integer, but complain if they're not lock-free. We only
* know how to check this on GCC. GCC on CentOS 7 / RHEL 7 is the
* targeted use case for these check.
*/
#ifndef HAVE_STDATOMIC_H
#ifdef __GNUC__
if (! __atomic_always_lock_free (sizeof (u_int64_t), 0)) {
#endif // __GNUC__
fprintf(stderr, "Warning: Cannot guarantee lock-free operation with 64-bit data types\n");
#ifdef __GNUC__
}
#endif // __GNUC__
#endif // HAVE_STDATOMIC_H
// XXX: Setting the process affinity requires root on most systems.
// Is this a feature we really need?
#ifdef TEST_PROC_AFFINITY
/* didn't seem to work.... */
/*
* increasing the priority of the process to minimise packet generation
* delay
*/
int rc = setpriority(PRIO_PROCESS, 0, -15);
if (rc < 0) {
perror("setpriority:");
fprintf(stderr, "setting priority to valid level\n");
rc = setpriority(PRIO_PROCESS, 0, 0);
}
/* setting the affinity of the process */
cpu_set_t cpu_set;
int affinity = -1;
int ncores = 1;
sched_getaffinity(0, sizeof(cpu_set_t), &cpu_set);
if (errno)
perror("couldn't get affinity:");
if ((ncores = sysconf(_SC_NPROCESSORS_CONF)) <= 0)
err("sysconf: couldn't get _SC_NPROCESSORS_CONF");
CPU_ZERO(&cpu_set);
CPU_SET(affinity, &cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu_set) != 0)
err("couldn't change CPU affinity");
#endif
test = iperf_new_test();
if (!test)
iperf_errexit(NULL, "create new test error - %s", iperf_strerror(i_errno));
iperf_defaults(test); /* sets defaults */
if (iperf_parse_arguments(test, argc, argv) < 0) {
iperf_err(test, "parameter error - %s", iperf_strerror(i_errno));
fprintf(stderr, "\n");
usage();
exit(1);
}
if (run(test) < 0)
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
iperf_free_test(test);
return 0;
}
static jmp_buf sigend_jmp_buf;
static int signed_sig;
static void __attribute__ ((noreturn))
sigend_handler(int sig)
{
signed_sig = sig;
longjmp(sigend_jmp_buf, 1);
}
/**************************************************************************/
static int
run(struct iperf_test *test)
{
/* Termination signals. */
iperf_catch_sigend(sigend_handler);
if (setjmp(sigend_jmp_buf))
iperf_got_sigend(test, signed_sig);
/* Ignore SIGPIPE to simplify error handling */
signal(SIGPIPE, SIG_IGN);
switch (test->role) {
case 's':
if (test->daemon) {
int rc;
rc = daemon(1, 0);
if (rc < 0) {
i_errno = IEDAEMON;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
}
if (iperf_create_pidfile(test) < 0) {
i_errno = IEPIDFILE;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
for (;;) {
int rc;
rc = iperf_run_server(test);
test->server_last_run_rc = rc;
if (rc < 0) {
iperf_err(test, "error - %s", iperf_strerror(i_errno));
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
}
iflush(test);
if (rc < -1) {
iperf_errexit(test, "exiting");
}
}
iperf_reset_test(test);
if (iperf_get_test_one_off(test) && rc != 2) {
/* Authentication failure doesn't count for 1-off test */
if (rc < 0 && i_errno == IEAUTHTEST) {
continue;
}
break;
}
}
iperf_delete_pidfile(test);
break;
case 'c':
if (iperf_create_pidfile(test) < 0) {
i_errno = IEPIDFILE;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
if (iperf_run_client(test) < 0)
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
iperf_delete_pidfile(test);
break;
default:
usage();
break;
}
iperf_catch_sigend(SIG_DFL);
signal(SIGPIPE, SIG_DFL);
return 0;
}
+664
View File
@@ -0,0 +1,664 @@
/*
* iperf, Copyright (c) 2014-2023, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include "iperf_config.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <assert.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#ifdef HAVE_SENDFILE
#ifdef linux
#include <sys/sendfile.h>
#else
#ifdef __FreeBSD__
#include <sys/uio.h>
#else
#if defined(__APPLE__) && defined(__MACH__) /* OS X */
#include <AvailabilityMacros.h>
#if defined(MAC_OS_X_VERSION_10_6)
#include <sys/uio.h>
#endif
#endif
#endif
#endif
#endif /* HAVE_SENDFILE */
#ifdef HAVE_POLL_H
#include <poll.h>
#endif /* HAVE_POLL_H */
#include "iperf.h"
#include "iperf_util.h"
#include "net.h"
#include "timer.h"
static int nread_read_timeout = 10;
static int nread_overall_timeout = 30;
/*
* Declaration of gerror in iperf_error.c. Most other files in iperf3 can get this
* by including "iperf.h", but net.c lives "below" this layer. Clearly the
* presence of this declaration is a sign we need to revisit this layering.
*/
extern int gerror;
/*
* timeout_connect adapted from netcat, via OpenBSD and FreeBSD
* Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
*/
int
timeout_connect(int s, const struct sockaddr *name, socklen_t namelen,
int timeout)
{
struct pollfd pfd;
socklen_t optlen;
int flags, optval;
int ret;
flags = 0;
if (timeout != -1) {
flags = fcntl(s, F_GETFL, 0);
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1)
return -1;
}
if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) {
pfd.fd = s;
pfd.events = POLLOUT;
if ((ret = poll(&pfd, 1, timeout)) == 1) {
optlen = sizeof(optval);
if ((ret = getsockopt(s, SOL_SOCKET, SO_ERROR,
&optval, &optlen)) == 0) {
errno = optval;
ret = optval == 0 ? 0 : -1;
}
} else if (ret == 0) {
errno = ETIMEDOUT;
ret = -1;
} else
ret = -1;
}
if (timeout != -1 && fcntl(s, F_SETFL, flags) == -1)
ret = -1;
return (ret);
}
/* netdial and netannouce code comes from libtask: http://swtch.com/libtask/
* Copyright: http://swtch.com/libtask/COPYRIGHT
*/
/* create a socket */
int
create_socket(int domain, int type, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, struct addrinfo **server_res_out)
{
struct addrinfo hints, *local_res = NULL, *server_res = NULL;
int s, saved_errno;
char portstr[6];
if (local) {
memset(&hints, 0, sizeof(hints));
hints.ai_family = domain;
hints.ai_socktype = type;
if ((gerror = getaddrinfo(local, NULL, &hints, &local_res)) != 0)
return -1;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = domain;
hints.ai_socktype = type;
snprintf(portstr, sizeof(portstr), "%d", port);
if ((gerror = getaddrinfo(server, portstr, &hints, &server_res)) != 0) {
if (local)
freeaddrinfo(local_res);
return -1;
}
s = socket(server_res->ai_family, type, proto);
if (s < 0) {
if (local)
freeaddrinfo(local_res);
freeaddrinfo(server_res);
return -1;
}
if (bind_dev) {
#if defined(HAVE_SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
bind_dev, IFNAMSIZ) < 0)
#endif // HAVE_SO_BINDTODEVICE
{
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
errno = saved_errno;
return -1;
}
}
/* Bind the local address if given a name (with or without --cport) */
if (local) {
if (local_port) {
struct sockaddr_in *lcladdr;
lcladdr = (struct sockaddr_in *)local_res->ai_addr;
lcladdr->sin_port = htons(local_port);
}
if (bind(s, (struct sockaddr *) local_res->ai_addr, local_res->ai_addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
errno = saved_errno;
return -1;
}
freeaddrinfo(local_res);
}
/* No local name, but --cport given */
else if (local_port) {
size_t addrlen;
struct sockaddr_storage lcl;
/* IPv4 */
if (server_res->ai_family == AF_INET) {
struct sockaddr_in *lcladdr = (struct sockaddr_in *) &lcl;
lcladdr->sin_family = AF_INET;
lcladdr->sin_port = htons(local_port);
lcladdr->sin_addr.s_addr = INADDR_ANY;
addrlen = sizeof(struct sockaddr_in);
}
/* IPv6 */
else if (server_res->ai_family == AF_INET6) {
struct sockaddr_in6 *lcladdr = (struct sockaddr_in6 *) &lcl;
lcladdr->sin6_family = AF_INET6;
lcladdr->sin6_port = htons(local_port);
lcladdr->sin6_addr = in6addr_any;
addrlen = sizeof(struct sockaddr_in6);
}
/* Unknown protocol */
else {
close(s);
freeaddrinfo(server_res);
errno = EAFNOSUPPORT;
return -1;
}
if (bind(s, (struct sockaddr *) &lcl, addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
return -1;
}
}
*server_res_out = server_res;
return s;
}
/* make connection to server */
int
netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout)
{
struct addrinfo *server_res = NULL;
int s, saved_errno;
s = create_socket(domain, proto, 0, local, bind_dev, local_port, server, port, &server_res);
if (s < 0) {
return -1;
}
if (timeout_connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen, timeout) < 0 && errno != EINPROGRESS) {
saved_errno = errno;
close(s);
freeaddrinfo(server_res);
errno = saved_errno;
return -1;
}
freeaddrinfo(server_res);
return s;
}
/***************************************************************/
int
netannounce(int domain, int proto, const char *local, const char *bind_dev, int port)
{
struct addrinfo hints, *res;
char portstr[6];
int s, opt, saved_errno;
snprintf(portstr, 6, "%d", port);
memset(&hints, 0, sizeof(hints));
/*
* If binding to the wildcard address with no explicit address
* family specified, then force us to get an AF_INET6 socket. On
* CentOS 6 and MacOS, getaddrinfo(3) with AF_UNSPEC in ai_family,
* and ai_flags containing AI_PASSIVE returns a result structure
* with ai_family set to AF_INET, with the result that we create
* and bind an IPv4 address wildcard address and by default, we
* can't accept IPv6 connections.
*
* On FreeBSD, under the above circumstances, ai_family in the
* result structure is set to AF_INET6.
*/
if (domain == AF_UNSPEC && !local) {
hints.ai_family = AF_INET6;
}
else {
hints.ai_family = domain;
}
hints.ai_socktype = proto;
hints.ai_flags = AI_PASSIVE;
if ((gerror = getaddrinfo(local, portstr, &hints, &res)) != 0)
return -1;
s = socket(res->ai_family, proto, 0);
if (s < 0) {
freeaddrinfo(res);
return -1;
}
if (bind_dev) {
#if defined(HAVE_SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
bind_dev, IFNAMSIZ) < 0)
#endif // HAVE_SO_BINDTODEVICE
{
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
}
opt = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(char *) &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
/*
* If we got an IPv6 socket, figure out if it should accept IPv4
* connections as well. We do that if and only if no address
* family was specified explicitly. Note that we can only
* do this if the IPV6_V6ONLY socket option is supported. Also,
* OpenBSD explicitly omits support for IPv4-mapped addresses,
* even though it implements IPV6_V6ONLY.
*/
#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
if (res->ai_family == AF_INET6 && (domain == AF_UNSPEC || domain == AF_INET6)) {
if (domain == AF_UNSPEC)
opt = 0;
else
opt = 1;
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *) &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
}
#endif /* IPV6_V6ONLY */
if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
freeaddrinfo(res);
if (proto == SOCK_STREAM) {
if (listen(s, INT_MAX) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
return -1;
}
}
return s;
}
/*******************************************************************/
/* Nread - reads 'count' bytes from a socket */
/********************************************************************/
int
Nread(int fd, char *buf, size_t count, int prot)
{
return Nrecv(fd, buf, count, prot, 0);
}
/*******************************************************************/
/* Nrecv - reads 'count' bytes from a socket */
/********************************************************************/
int
Nrecv(int fd, char *buf, size_t count, int prot, int sock_opt)
{
register ssize_t r;
// `nleft` must be signed as it may get negative value for SKIP-RX-COPY UDP (MSG_TRUNC in sock_opt).
register ssize_t nleft = count;
register size_t total = 0;
struct iperf_time ftimeout = { 0, 0 };
fd_set rfdset;
struct timeval timeout = { nread_read_timeout, 0 };
/*
* fd might not be ready for reading on entry. Check for this
* (with timeout) first.
*
* This check could go inside the while() loop below, except we're
* currently considering whether it might make sense to support a
* codepath that bypasses this check, for situations where we
* already know that fd has data on it (for example if we'd gotten
* to here as the result of a select() call.
*/
{
FD_ZERO(&rfdset);
FD_SET(fd, &rfdset);
r = select(fd + 1, &rfdset, NULL, NULL, &timeout);
if (r < 0) {
return NET_HARDERROR;
}
if (r == 0) {
return 0;
}
}
while (nleft > 0) {
if (sock_opt)
r = recv(fd, buf, nleft, sock_opt);
else
r = read(fd, buf, nleft);
if (r < 0) {
/* XXX EWOULDBLOCK can't happen without non-blocking sockets */
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
break;
else
return NET_HARDERROR;
} else if (r == 0)
break;
total += r;
nleft -= r;
buf += r;
/*
* We need some more bytes but don't want to wait around
* forever for them. In the case of partial results, we need
* to be able to read some bytes every nread_timeout seconds.
*/
if (nleft > 0) {
struct iperf_time now;
/*
* Also, we have an approximate upper limit for the total time
* that a Nread call is supposed to take. We trade off accuracy
* of this timeout for a hopefully lower performance impact.
*/
iperf_time_now(&now);
if (ftimeout.secs == 0) {
ftimeout = now;
iperf_time_add_usecs(&ftimeout, nread_overall_timeout * 1000000L);
}
if (iperf_time_compare(&ftimeout, &now) < 0) {
break;
}
FD_ZERO(&rfdset);
FD_SET(fd, &rfdset);
r = select(fd + 1, &rfdset, NULL, NULL, &timeout);
if (r < 0) {
return NET_HARDERROR;
}
if (r == 0) {
break;
}
}
}
return total;
}
/********************************************************************/
/* Nreads 'count' bytes from a socket - but without using select() */
/********************************************************************/
int
Nread_no_select(int fd, char *buf, size_t count, int prot)
{
return Nrecv_no_select(fd, buf, count, prot, 0);
}
/********************************************************************/
/* Nrecv reads 'count' bytes from a socket - but without using select() */
/********************************************************************/
int
Nrecv_no_select(int fd, char *buf, size_t count, int prot, int sock_opt)
{
register ssize_t r;
register size_t nleft = count;
while (nleft > 0) {
if (sock_opt)
r = recv(fd, buf, nleft, sock_opt);
else
r = read(fd, buf, nleft);
if (r < 0) {
/* XXX EWOULDBLOCK can't happen without non-blocking sockets */
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
break;
else
return NET_HARDERROR;
} else if (r == 0)
break;
nleft -= r;
buf += r;
}
return count - nleft;
}
/*
* N W R I T E
*/
int
Nwrite(int fd, const char *buf, size_t count, int prot)
{
register ssize_t r;
register size_t nleft = count;
while (nleft > 0) {
r = write(fd, buf, nleft);
if (r < 0) {
switch (errno) {
case EINTR:
case EAGAIN:
#if (EAGAIN != EWOULDBLOCK)
/* XXX EWOULDBLOCK can't happen without non-blocking sockets */
case EWOULDBLOCK:
#endif
if (count == nleft)
return NET_SOFTERROR;
return count - nleft;
case ENOBUFS :
return NET_SOFTERROR;
default:
return NET_HARDERROR;
}
} else if (r == 0)
return NET_SOFTERROR;
nleft -= r;
buf += r;
}
return count;
}
int
has_sendfile(void)
{
#if defined(HAVE_SENDFILE)
return 1;
#else /* HAVE_SENDFILE */
return 0;
#endif /* HAVE_SENDFILE */
}
/*
* N S E N D F I L E
*/
int
Nsendfile(int fromfd, int tofd, const char *buf, size_t count)
{
#if defined(HAVE_SENDFILE)
off_t offset;
#if defined(__FreeBSD__) || (defined(__APPLE__) && defined(__MACH__) && defined(MAC_OS_X_VERSION_10_6))
off_t sent;
#endif
register size_t nleft;
register ssize_t r;
nleft = count;
while (nleft > 0) {
offset = count - nleft;
#ifdef linux
r = sendfile(tofd, fromfd, &offset, nleft);
if (r > 0)
nleft -= r;
#elif defined(__FreeBSD__)
r = sendfile(fromfd, tofd, offset, nleft, NULL, &sent, 0);
nleft -= sent;
#elif defined(__APPLE__) && defined(__MACH__) && defined(MAC_OS_X_VERSION_10_6) /* OS X */
sent = nleft;
r = sendfile(fromfd, tofd, offset, &sent, NULL, 0);
nleft -= sent;
#else
/* Shouldn't happen. */
r = -1;
errno = ENOSYS;
#endif
if (r < 0) {
switch (errno) {
case EINTR:
case EAGAIN:
#if (EAGAIN != EWOULDBLOCK)
/* XXX EWOULDBLOCK can't happen without non-blocking sockets */
case EWOULDBLOCK:
#endif
if (count == nleft)
return NET_SOFTERROR;
return count - nleft;
case ENOBUFS:
case ENOMEM:
return NET_SOFTERROR;
default:
return NET_HARDERROR;
}
}
#ifdef linux
else if (r == 0)
return NET_SOFTERROR;
#endif
}
return count;
#else /* HAVE_SENDFILE */
errno = ENOSYS; /* error if somehow get called without HAVE_SENDFILE */
return NET_HARDERROR;
#endif /* HAVE_SENDFILE */
}
/*************************************************************************/
int
setnonblocking(int fd, int nonblocking)
{
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
perror("fcntl(F_GETFL)");
return -1;
}
if (nonblocking)
newflags = flags | (int) O_NONBLOCK;
else
newflags = flags & ~((int) O_NONBLOCK);
if (newflags != flags)
if (fcntl(fd, F_SETFL, newflags) < 0) {
perror("fcntl(F_SETFL)");
return -1;
}
return 0;
}
/****************************************************************************/
int
getsockdomain(int sock)
{
struct sockaddr_storage sa;
socklen_t len = sizeof(sa);
if (getsockname(sock, (struct sockaddr *)&sa, &len) < 0) {
return -1;
}
return ((struct sockaddr *) &sa)->sa_family;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* iperf, Copyright (c) 2014, 2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#ifndef __NET_H
#define __NET_H
int timeout_connect(int s, const struct sockaddr *name, socklen_t namelen, int timeout);
int create_socket(int domain, int type, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, struct addrinfo **server_res_out);
int netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout);
int netannounce(int domain, int proto, const char *local, const char *bind_dev, int port);
int Nread(int fd, char *buf, size_t count, int prot);
int Nrecv(int fd, char *buf, size_t count, int prot, int sock_opt);
int Nread_no_select(int fd, char *buf, size_t count, int prot);
int Nrecv_no_select(int fd, char *buf, size_t count, int prot, int sock_opt);
int Nwrite(int fd, const char *buf, size_t count, int prot) /* __attribute__((hot)) */;
int has_sendfile(void);
int Nsendfile(int fromfd, int tofd, const char *buf, size_t count) /* __attribute__((hot)) */;
int setnonblocking(int fd, int nonblocking);
int getsockdomain(int sock);
int parse_qos(const char *tos);
#define NET_SOFTERROR -1
#define NET_HARDERROR -2
#endif /* __NET_H */
@@ -0,0 +1,166 @@
// "License": Public Domain
// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
#ifndef PORTABLE_ENDIAN_H__
#define PORTABLE_ENDIAN_H__
#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
# define __WINDOWS__
#endif
#if defined(__CYGWIN__)
# include <endian.h>
#elif defined(HAVE_ENDIAN_H)
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
# if defined(__sgi)
# include <netinet/in.h>
# include <inttypes.h>
# define be64toh(x) (x)
# define htobe64(x) (x)
# endif
#elif defined(__APPLE__)
# include <libkern/OSByteOrder.h>
# define htobe16(x) OSSwapHostToBigInt16(x)
# define htole16(x) OSSwapHostToLittleInt16(x)
# define be16toh(x) OSSwapBigToHostInt16(x)
# define le16toh(x) OSSwapLittleToHostInt16(x)
# define htobe32(x) OSSwapHostToBigInt32(x)
# define htole32(x) OSSwapHostToLittleInt32(x)
# define be32toh(x) OSSwapBigToHostInt32(x)
# define le32toh(x) OSSwapLittleToHostInt32(x)
# define htobe64(x) OSSwapHostToBigInt64(x)
# define htole64(x) OSSwapHostToLittleInt64(x)
# define be64toh(x) OSSwapBigToHostInt64(x)
# define le64toh(x) OSSwapLittleToHostInt64(x)
# define __BYTE_ORDER BYTE_ORDER
# define __BIG_ENDIAN BIG_ENDIAN
# define __LITTLE_ENDIAN LITTLE_ENDIAN
# define __PDP_ENDIAN PDP_ENDIAN
#elif defined(__sun) && defined(__SVR4)
# include <sys/types.h>
# include <netinet/in.h>
# include <inttypes.h>
# if !defined (ntohll) || !defined(htonll)
# ifdef _BIG_ENDIAN
# define htonll(x) (x)
# define ntohll(x) (x)
# else
# define htonll(x) ((((uint64_t)htonl(x)) << 32) + htonl((uint64_t)(x) >> 32))
# define ntohll(x) ((((uint64_t)ntohl(x)) << 32) + ntohl((uint64_t)(x) >> 32))
# endif
# endif
# define be64toh(x) ntohll(x)
# define htobe64(x) htonll(x)
#elif defined(__WINDOWS__)
# include <winsock2.h>
# include <sys/param.h>
# if BYTE_ORDER == LITTLE_ENDIAN
# define htobe16(x) htons(x)
# define htole16(x) (x)
# define be16toh(x) ntohs(x)
# define le16toh(x) (x)
# define htobe32(x) htonl(x)
# define htole32(x) (x)
# define be32toh(x) ntohl(x)
# define le32toh(x) (x)
# define htobe64(x) htonll(x)
# define htole64(x) (x)
# define be64toh(x) ntohll(x)
# define le64toh(x) (x)
# elif BYTE_ORDER == BIG_ENDIAN
/* that would be xbox 360 */
# define htobe16(x) (x)
# define htole16(x) __builtin_bswap16(x)
# define be16toh(x) (x)
# define le16toh(x) __builtin_bswap16(x)
# define htobe32(x) (x)
# define htole32(x) __builtin_bswap32(x)
# define be32toh(x) (x)
# define le32toh(x) __builtin_bswap32(x)
# define htobe64(x) (x)
# define htole64(x) __builtin_bswap64(x)
# define be64toh(x) (x)
# define le64toh(x) __builtin_bswap64(x)
# else
# error byte order not supported
# endif
# define __BYTE_ORDER BYTE_ORDER
# define __BIG_ENDIAN BIG_ENDIAN
# define __LITTLE_ENDIAN LITTLE_ENDIAN
# define __PDP_ENDIAN PDP_ENDIAN
#else
// Unsupported platforms.
// Intended to support CentOS 5 but hopefully not too far from
// the truth because we use the homebrew htonll, et al. implementations
// that were originally the sole implementation of this functionality
// in iperf 3.0.
#if (!defined(__vxworks)) && (!defined(__VXWORKS__))
# warning platform not supported
#endif
# include <endian.h>
#if BYTE_ORDER == BIG_ENDIAN
#define HTONLL(n) (n)
#define NTOHLL(n) (n)
#else
#define HTONLL(n) ((((unsigned long long)(n) & 0xFF) << 56) | \
(((unsigned long long)(n) & 0xFF00) << 40) | \
(((unsigned long long)(n) & 0xFF0000) << 24) | \
(((unsigned long long)(n) & 0xFF000000) << 8) | \
(((unsigned long long)(n) & 0xFF00000000) >> 8) | \
(((unsigned long long)(n) & 0xFF0000000000) >> 24) | \
(((unsigned long long)(n) & 0xFF000000000000) >> 40) | \
(((unsigned long long)(n) & 0xFF00000000000000) >> 56))
#define NTOHLL(n) ((((unsigned long long)(n) & 0xFF) << 56) | \
(((unsigned long long)(n) & 0xFF00) << 40) | \
(((unsigned long long)(n) & 0xFF0000) << 24) | \
(((unsigned long long)(n) & 0xFF000000) << 8) | \
(((unsigned long long)(n) & 0xFF00000000) >> 8) | \
(((unsigned long long)(n) & 0xFF0000000000) >> 24) | \
(((unsigned long long)(n) & 0xFF000000000000) >> 40) | \
(((unsigned long long)(n) & 0xFF00000000000000) >> 56))
#endif
#define htobe64(n) HTONLL(n)
#define be64toh(n) NTOHLL(n)
#endif
#endif
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwVbvPf/eDIZKmEVth9+VPgSx1RkXOAPCJ5tl51bcYoy9P10N
noutsTK/64VclIuyUDUdAw81Vu5tYRcBeB8Jllp02xL8kPo0IROsT3wmMYbPziUG
/I2988sAP9mL8QbtKydnADHMikfadfyPkfxW2naFtquWT/vKKVkhC2LHJyTpmAVj
pp6R9LDDu/YY0/kb5DvYPpe62xgNWjNVIhABu3R+StAAL25SaLXuaGSVpDe6Sphn
TqVf+LmKCkZmSMSfQoozXomNyFpLU8ODoV3QyHYi6QSWWFtU6gu0uso/9pfRFjbN
GV9lxokKNz/cZGyu0SFddMiTGrt24NROi0RHwwIDAQABAoIBACVG+cHef5WyntdV
K5UzCrd2eEM6HzvxnZG9aJx+JufpcuOwsVuMWuT7f/2NLRiHBs5oLzvTxtkIB5bc
tK/QbCzNLBLBSmk5lKt7+5EnwsVx1MdOZFZ1jdZfoaCt9Ul3qGrVogprj6Bp0jlF
hPkEyko85/McilLJnWTzhmeHmBZ3tOJ9LWHgVdXGXZx/pBuxZ+UB/xgZdvdZK86F
xndWqD8lvfpoSsVwzCORdXvQWs78CtT2KXYvt8S6mrLMoIoHLO/1rwwKKoZfFuja
NouN36PGaSo4/9O1d11/s9zwdJH3ShozY0Fao8I3XhdH0uvTirmAEPh7U1Lo71pP
ksUvbUECgYEA/7p1Bg/XsSK72KgJ695B2hi8g9+wv0eKCpGwkKJQZKE338it8zIn
+JMaeDjObLhb4B2QP/3iL0mdU2mJzm0X8hYCKqBdyICb9wixznLLAej+uw2j60Ef
tlGenCqAkVL3pRS5txztNhFsXx3JwxCSj4zwmVm5oyxkpAeuwvZ1cPkCgYEAwYuC
6nPlRf6GEf/MuHCziaCCPrDm1u472uspxHn41crw6BaMl5xcmr0Z8O7heOtlVmMl
Mn3gfIbd/NGyjs8ejmWcO5mZHtESGM/zAaVyu7bn3ij37OEndbEpZsCpRtxvtaji
MTMwhdV2xteJ0+KCtg66ziSyJv3krQTEW8DZKZsCgYB8BcnLbtOErPu9T4HASsJV
K7oBmvL1UZS5G38uJgonQ6j9dy4lzCVmgLFNrP8v6xljz/Ktlkuj82fBlGWpH2+F
kPbsBWp2WylI3YaeQT4DZyRjQ3JEHglrOppZ0qMX180S2sJW9Eh2+Gw+lQvM9rSd
uhTVypYldNo6Ux+GnlDGwQKBgQC/6W4uvCyjcvXN8y8z08yysw1yzEaY6DFBqd0I
jUlH9Ktb9sABtXG9nbSTSssX85HQTw8bOeXWlISZo/TB1m4eFHMORgemnviq0cfL
4hoaOAtCJq1vnPJbqQe8c11mfj3mi0d+MZvzmO7ly+NGzlt92q0wqwJb13VgelGa
CWdL8QKBgQD1ABK8WJuMowihrQsLaUxNO/1hsN8CP/rjoi9D5NdSFt5zzcC3D/RA
m42ScOaAFIh44Js5aAROvbDqIlelhwRlmutP/lYyQRZDZm7X6u/bKuqK4H/sM6ZB
SICe2eWFvbKexI7QnreRltOWIFrNoZy9FdELM8DS+kePAlVt7c83Uw==
-----END RSA PRIVATE KEY-----
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwVbvPf/eDIZKmEVth9+V
PgSx1RkXOAPCJ5tl51bcYoy9P10NnoutsTK/64VclIuyUDUdAw81Vu5tYRcBeB8J
llp02xL8kPo0IROsT3wmMYbPziUG/I2988sAP9mL8QbtKydnADHMikfadfyPkfxW
2naFtquWT/vKKVkhC2LHJyTpmAVjpp6R9LDDu/YY0/kb5DvYPpe62xgNWjNVIhAB
u3R+StAAL25SaLXuaGSVpDe6SphnTqVf+LmKCkZmSMSfQoozXomNyFpLU8ODoV3Q
yHYi6QSWWFtU6gu0uso/9pfRFjbNGV9lxokKNz/cZGyu0SFddMiTGrt24NROi0RH
wwIDAQAB
-----END PUBLIC KEY-----
+527
View File
@@ -0,0 +1,527 @@
/* $OpenBSD: queue.h,v 1.32 2007/04/30 18:42:34 pedro Exp $ */
/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
/*
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
*/
#ifndef _SYS_QUEUE_H_
#define _SYS_QUEUE_H_
/*
* This file defines five types of data structures: singly-linked lists,
* lists, simple queues, tail queues, and circular queues.
*
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may only be traversed in the forward direction.
*
* A simple queue is headed by a pair of pointers, one the head of the
* list and the other to the tail of the list. The elements are singly
* linked to save space, so elements can only be removed from the
* head of the list. New elements can be added to the list before or after
* an existing element, at the head of the list, or at the end of the
* list. A simple queue may only be traversed in the forward direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* A circle queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or after
* an existing element, at the head of the list, or at the end of the list.
* A circle queue may be traversed in either direction, but has a more
* complex end of list detection.
*
* For details on the use of these macros, see the queue(3) manual page.
*/
#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
#define _Q_INVALIDATE(a) (a) = ((void *)-1)
#else
#define _Q_INVALIDATE(a)
#endif
/*
* Singly-linked List definitions.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
/*
* Singly-linked List access methods.
*/
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_END(head) NULL
#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_FOREACH(var, head, field) \
for((var) = SLIST_FIRST(head); \
(var) != SLIST_END(head); \
(var) = SLIST_NEXT(var, field))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != SLIST_END(head); \
(varp) = &SLIST_NEXT((var), field))
/*
* Singly-linked List functions.
*/
#define SLIST_INIT(head) { \
SLIST_FIRST(head) = SLIST_END(head); \
}
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
(elm)->field.sle_next = (slistelm)->field.sle_next; \
(slistelm)->field.sle_next = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
(elm)->field.sle_next = (head)->slh_first; \
(head)->slh_first = (elm); \
} while (0)
#define SLIST_REMOVE_NEXT(head, elm, field) do { \
(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
(head)->slh_first = (head)->slh_first->field.sle_next; \
} while (0)
#define SLIST_REMOVE(head, elm, type, field) do { \
if ((head)->slh_first == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} else { \
struct type *curelm = (head)->slh_first; \
\
while (curelm->field.sle_next != (elm)) \
curelm = curelm->field.sle_next; \
curelm->field.sle_next = \
curelm->field.sle_next->field.sle_next; \
_Q_INVALIDATE((elm)->field.sle_next); \
} \
} while (0)
/*
* List definitions.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
/*
* List access methods
*/
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_END(head) NULL
#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_FOREACH(var, head, field) \
for((var) = LIST_FIRST(head); \
(var)!= LIST_END(head); \
(var) = LIST_NEXT(var, field))
/*
* List functions.
*/
#define LIST_INIT(head) do { \
LIST_FIRST(head) = LIST_END(head); \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
(listelm)->field.le_next->field.le_prev = \
&(elm)->field.le_next; \
(listelm)->field.le_next = (elm); \
(elm)->field.le_prev = &(listelm)->field.le_next; \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.le_prev = (listelm)->field.le_prev; \
(elm)->field.le_next = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &(elm)->field.le_next; \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
if (((elm)->field.le_next = (head)->lh_first) != NULL) \
(head)->lh_first->field.le_prev = &(elm)->field.le_next;\
(head)->lh_first = (elm); \
(elm)->field.le_prev = &(head)->lh_first; \
} while (0)
#define LIST_REMOVE(elm, field) do { \
if ((elm)->field.le_next != NULL) \
(elm)->field.le_next->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = (elm)->field.le_next; \
_Q_INVALIDATE((elm)->field.le_prev); \
_Q_INVALIDATE((elm)->field.le_next); \
} while (0)
#define LIST_REPLACE(elm, elm2, field) do { \
if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
(elm2)->field.le_next->field.le_prev = \
&(elm2)->field.le_next; \
(elm2)->field.le_prev = (elm)->field.le_prev; \
*(elm2)->field.le_prev = (elm2); \
_Q_INVALIDATE((elm)->field.le_prev); \
_Q_INVALIDATE((elm)->field.le_next); \
} while (0)
/*
* Simple queue definitions.
*/
#define SIMPLEQ_HEAD(name, type) \
struct name { \
struct type *sqh_first; /* first element */ \
struct type **sqh_last; /* addr of last next element */ \
}
#define SIMPLEQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).sqh_first }
#define SIMPLEQ_ENTRY(type) \
struct { \
struct type *sqe_next; /* next element */ \
}
/*
* Simple queue access methods.
*/
#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
#define SIMPLEQ_END(head) NULL
#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
#define SIMPLEQ_FOREACH(var, head, field) \
for((var) = SIMPLEQ_FIRST(head); \
(var) != SIMPLEQ_END(head); \
(var) = SIMPLEQ_NEXT(var, field))
/*
* Simple queue functions.
*/
#define SIMPLEQ_INIT(head) do { \
(head)->sqh_first = NULL; \
(head)->sqh_last = &(head)->sqh_first; \
} while (0)
#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
(head)->sqh_last = &(elm)->field.sqe_next; \
(head)->sqh_first = (elm); \
} while (0)
#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.sqe_next = NULL; \
*(head)->sqh_last = (elm); \
(head)->sqh_last = &(elm)->field.sqe_next; \
} while (0)
#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
(head)->sqh_last = &(elm)->field.sqe_next; \
(listelm)->field.sqe_next = (elm); \
} while (0)
#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
(head)->sqh_last = &(head)->sqh_first; \
} while (0)
/*
* Tail queue definitions.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
/*
* tail queue access methods
*/
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_END(head) NULL
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
/* XXX */
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head) \
(TAILQ_FIRST(head) == TAILQ_END(head))
#define TAILQ_FOREACH(var, head, field) \
for((var) = TAILQ_FIRST(head); \
(var) != TAILQ_END(head); \
(var) = TAILQ_NEXT(var, field))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for((var) = TAILQ_LAST(head, headname); \
(var) != TAILQ_END(head); \
(var) = TAILQ_PREV(var, headname, field))
/*
* Tail queue functions.
*/
#define TAILQ_INIT(head) do { \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
(head)->tqh_first->field.tqe_prev = \
&(elm)->field.tqe_next; \
else \
(head)->tqh_last = &(elm)->field.tqe_next; \
(head)->tqh_first = (elm); \
(elm)->field.tqe_prev = &(head)->tqh_first; \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.tqe_next = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &(elm)->field.tqe_next; \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
(elm)->field.tqe_next->field.tqe_prev = \
&(elm)->field.tqe_next; \
else \
(head)->tqh_last = &(elm)->field.tqe_next; \
(listelm)->field.tqe_next = (elm); \
(elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
(elm)->field.tqe_next = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
} while (0)
#define TAILQ_REMOVE(head, elm, field) do { \
if (((elm)->field.tqe_next) != NULL) \
(elm)->field.tqe_next->field.tqe_prev = \
(elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = (elm)->field.tqe_next; \
_Q_INVALIDATE((elm)->field.tqe_prev); \
_Q_INVALIDATE((elm)->field.tqe_next); \
} while (0)
#define TAILQ_REPLACE(head, elm, elm2, field) do { \
if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
(elm2)->field.tqe_next->field.tqe_prev = \
&(elm2)->field.tqe_next; \
else \
(head)->tqh_last = &(elm2)->field.tqe_next; \
(elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
*(elm2)->field.tqe_prev = (elm2); \
_Q_INVALIDATE((elm)->field.tqe_prev); \
_Q_INVALIDATE((elm)->field.tqe_next); \
} while (0)
/*
* Circular queue definitions.
*/
#define CIRCLEQ_HEAD(name, type) \
struct name { \
struct type *cqh_first; /* first element */ \
struct type *cqh_last; /* last element */ \
}
#define CIRCLEQ_HEAD_INITIALIZER(head) \
{ CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
#define CIRCLEQ_ENTRY(type) \
struct { \
struct type *cqe_next; /* next element */ \
struct type *cqe_prev; /* previous element */ \
}
/*
* Circular queue access methods
*/
#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
#define CIRCLEQ_LAST(head) ((head)->cqh_last)
#define CIRCLEQ_END(head) ((void *)(head))
#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
#define CIRCLEQ_EMPTY(head) \
(CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
#define CIRCLEQ_FOREACH(var, head, field) \
for((var) = CIRCLEQ_FIRST(head); \
(var) != CIRCLEQ_END(head); \
(var) = CIRCLEQ_NEXT(var, field))
#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
for((var) = CIRCLEQ_LAST(head); \
(var) != CIRCLEQ_END(head); \
(var) = CIRCLEQ_PREV(var, field))
/*
* Circular queue functions.
*/
#define CIRCLEQ_INIT(head) do { \
(head)->cqh_first = CIRCLEQ_END(head); \
(head)->cqh_last = CIRCLEQ_END(head); \
} while (0)
#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
(elm)->field.cqe_next = (listelm)->field.cqe_next; \
(elm)->field.cqe_prev = (listelm); \
if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
(head)->cqh_last = (elm); \
else \
(listelm)->field.cqe_next->field.cqe_prev = (elm); \
(listelm)->field.cqe_next = (elm); \
} while (0)
#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
(elm)->field.cqe_next = (listelm); \
(elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
(head)->cqh_first = (elm); \
else \
(listelm)->field.cqe_prev->field.cqe_next = (elm); \
(listelm)->field.cqe_prev = (elm); \
} while (0)
#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
(elm)->field.cqe_next = (head)->cqh_first; \
(elm)->field.cqe_prev = CIRCLEQ_END(head); \
if ((head)->cqh_last == CIRCLEQ_END(head)) \
(head)->cqh_last = (elm); \
else \
(head)->cqh_first->field.cqe_prev = (elm); \
(head)->cqh_first = (elm); \
} while (0)
#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.cqe_next = CIRCLEQ_END(head); \
(elm)->field.cqe_prev = (head)->cqh_last; \
if ((head)->cqh_first == CIRCLEQ_END(head)) \
(head)->cqh_first = (elm); \
else \
(head)->cqh_last->field.cqe_next = (elm); \
(head)->cqh_last = (elm); \
} while (0)
#define CIRCLEQ_REMOVE(head, elm, field) do { \
if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
(head)->cqh_last = (elm)->field.cqe_prev; \
else \
(elm)->field.cqe_next->field.cqe_prev = \
(elm)->field.cqe_prev; \
if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
(head)->cqh_first = (elm)->field.cqe_next; \
else \
(elm)->field.cqe_prev->field.cqe_next = \
(elm)->field.cqe_next; \
_Q_INVALIDATE((elm)->field.cqe_prev); \
_Q_INVALIDATE((elm)->field.cqe_next); \
} while (0)
#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \
if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \
CIRCLEQ_END(head)) \
(head).cqh_last = (elm2); \
else \
(elm2)->field.cqe_next->field.cqe_prev = (elm2); \
if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \
CIRCLEQ_END(head)) \
(head).cqh_first = (elm2); \
else \
(elm2)->field.cqe_prev->field.cqe_next = (elm2); \
_Q_INVALIDATE((elm)->field.cqe_prev); \
_Q_INVALIDATE((elm)->field.cqe_next); \
} while (0)
#endif /* !_SYS_QUEUE_H_ */
+90
View File
@@ -0,0 +1,90 @@
/*
* iperf, Copyright (c) 2017-2020, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "iperf.h"
#include "iperf_api.h"
#include "version.h"
#include "units.h"
int test_iperf_set_test_bind_port(struct iperf_test *test)
{
int port;
port = iperf_get_test_bind_port(test);
iperf_set_test_bind_port(test, 5202);
port = iperf_get_test_bind_port(test);
assert(port == 5202);
return 0;
}
int test_iperf_set_mss(struct iperf_test *test)
{
int mss = iperf_get_test_mss(test);
iperf_set_test_mss(test, 535);
mss = iperf_get_test_mss(test);
assert(mss == 535);
return 0;
}
int
main(int argc, char **argv)
{
const char *ver;
struct iperf_test *test;
int sint, gint;
ver = iperf_get_iperf_version();
assert(strcmp(ver, IPERF_VERSION) == 0);
test = iperf_new_test();
assert(test != NULL);
iperf_defaults(test);
sint = 10;
iperf_set_test_connect_timeout(test, sint);
gint = iperf_get_test_connect_timeout(test);
assert(sint == gint);
int ret;
ret = test_iperf_set_test_bind_port(test);
ret += test_iperf_set_mss(test);
if (ret < 0)
{
return -1;
}
return 0;
}
+124
View File
@@ -0,0 +1,124 @@
/*
* iperf, Copyright (c) 2020, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include "iperf_config.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "iperf.h"
#include "iperf_api.h"
#if defined(HAVE_SSL)
#include "iperf_auth.h"
#endif /* HAVE_SSL */
#include "version.h"
#include "units.h"
#if defined(HAVE_SSL)
int test_authtoken(const char *authUser, const char *authPassword, EVP_PKEY *pubkey, EVP_PKEY *privkey);
int
main(int argc, char **argv)
{
/* sha256 */
void sha256(const char *string, char outputBuffer[65]);
const char sha256String[] = "This is a SHA256 test.";
const char sha256Digest[] = "4816482f8b4149f687a1a33d61a0de6b611364ec0fb7adffa59ff2af672f7232"; /* echo -n "This is a SHA256 test." | shasum -a256 */
char sha256Output[65];
sha256(sha256String, sha256Output);
assert(strcmp(sha256Output, sha256Digest) == 0);
/* Base64{Encode,Decode} */
int Base64Encode(const unsigned char* buffer, const size_t length, char** b64text);
int Base64Decode(const char* b64message, unsigned char** buffer, size_t* length);
const char base64String[] = "This is a Base64 test.";
char *base64Text;
char *base64Decode;
size_t base64DecodeLength;
const char base64EncodeCheck[] = "VGhpcyBpcyBhIEJhc2U2NCB0ZXN0Lg=="; /* echo -n "This is a Base64 test." | b64encode -r - */
assert(Base64Encode((unsigned char *) base64String, strlen(base64String), &base64Text) == 0);
assert(strcmp(base64Text, base64EncodeCheck) == 0);
assert(Base64Decode(base64Text, (unsigned char **) &base64Decode, &base64DecodeLength) == 0);
assert(strcmp(base64String, base64Decode) == 0);
/* public/private key tests */
const char *pubkeyfile = "public.pem";
const char *privkeyfile = "private.pem";
/* built-in tests */
assert(test_load_pubkey_from_file(pubkeyfile) == 0);
assert(test_load_private_key_from_file(privkeyfile) == 0);
/* load public key pair for use in further tests */
EVP_PKEY *pubkey, *privkey;
pubkey = load_pubkey_from_file(pubkeyfile);
assert(pubkey);
privkey = load_privkey_from_file(privkeyfile);
assert(privkey);
/* authentication token tests */
assert(test_authtoken("kilroy", "fubar", pubkey, privkey) == 0);
/* This should fail because the data is way too long for the RSA key */
/* assert(test_authtoken("kilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroykilroy", "fubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubarfubar", pubkey, privkey) < 0); */
return 0;
}
int
test_authtoken(const char *authUser, const char *authPassword, EVP_PKEY *pubkey, EVP_PKEY *privkey) {
char *authToken;
char *decodeUser;
char *decodePassword;
time_t decodeTime;
int use_pkcs1_padding = 1;
assert(encode_auth_setting(authUser, authPassword, pubkey, &authToken, use_pkcs1_padding) == 0);
assert(decode_auth_setting(0, authToken, privkey, &decodeUser, &decodePassword, &decodeTime, use_pkcs1_padding) == 0);
assert(strcmp(decodeUser, authUser) == 0);
assert(strcmp(decodePassword, authPassword) == 0);
time_t now = time(NULL);
assert(now - decodeTime >= 0); /* time has to go forwards */
assert(now - decodeTime <= 1); /* shouldn't take more than a second to run */
return 0;
}
#else
int
main(int argc, char **argv)
{
return 0;
}
#endif /* HAVE_SSL */
+81
View File
@@ -0,0 +1,81 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include "iperf_config.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include "timer.h"
#include "iperf_time.h"
static int flag;
static void
timer_proc( TimerClientData client_data, struct iperf_time* nowP )
{
flag = 1;
}
int
main(int argc, char **argv)
{
Timer *tp;
flag = 0;
tp = tmr_create(NULL, timer_proc, JunkClientData, 3000000, 0);
if (!tp)
{
printf("failed to create timer\n");
exit(-1);
}
sleep(2);
tmr_run(NULL);
if (flag)
{
printf("timer should not have expired\n");
exit(-1);
}
sleep(1);
tmr_run(NULL);
if (!flag)
{
printf("timer should have expired\n");
exit(-2);
}
tmr_destroy();
exit(0);
}
+108
View File
@@ -0,0 +1,108 @@
/*
* iperf, Copyright (c) 2014, 2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "iperf.h"
#include "units.h"
int
main(int argc, char **argv)
{
iperf_size_t llu;
double d;
char s[11];
assert(1024.0 * 0.5 == unit_atof("0.5K"));
assert(1024.0 == unit_atof("1K"));
assert(1024.0 * 1024.0 == unit_atof("1M"));
assert(4.0 * 1024.0 * 1024.0 * 1024.0 == unit_atof("4G"));
assert(3.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 == unit_atof("3T"));
assert(1024.0 * 0.5 == unit_atof("0.5k"));
assert(1024.0 == unit_atof("1k"));
assert(1024.0 * 1024.0 == unit_atof("1m"));
assert(4.0 * 1024.0 * 1024.0 * 1024.0 == unit_atof("4g"));
assert(3.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 == unit_atof("3t"));
assert(1024 * 0.5 == unit_atoi("0.5K"));
assert(1024 == unit_atoi("1K"));
assert(1024 * 1024 == unit_atoi("1M"));
d = 4.0 * 1024 * 1024 * 1024;
llu = (iperf_size_t) d;
assert(llu == unit_atoi("4G"));
d = 3.0 * 1024 * 1024 * 1024 * 1024;
llu = (iperf_size_t) d;
assert(llu == unit_atoi("3T"));
assert(1024 * 0.5 == unit_atoi("0.5k"));
assert(1024 == unit_atoi("1k"));
assert(1024 * 1024 == unit_atoi("1m"));
d = 4.0 * 1024 * 1024 * 1024;
llu = (iperf_size_t) d;
assert(llu == unit_atoi("4g"));
d = 3.0 * 1024 * 1024 * 1024 * 1024;
llu = (iperf_size_t) d;
assert(llu == unit_atoi("3t"));
unit_snprintf(s, 11, 1024.0, 'A');
assert(strncmp(s, "1.00 KByte", 11) == 0);
unit_snprintf(s, 11, 1024.0 * 1024.0, 'A');
assert(strncmp(s, "1.00 MByte", 11) == 0);
unit_snprintf(s, 11, 1000.0, 'k');
assert(strncmp(s, "8.00 Kbit", 11) == 0);
unit_snprintf(s, 11, 1000.0 * 1000.0, 'a');
assert(strncmp(s, "8.00 Mbit", 11) == 0);
d = 4.0 * 1024 * 1024 * 1024;
unit_snprintf(s, 11, d, 'A');
assert(strncmp(s, "4.00 GByte", 11) == 0);
unit_snprintf(s, 11, d, 'a');
assert(strncmp(s, "34.4 Gbit", 11) == 0);
d = 4.0 * 1024 * 1024 * 1024 * 1024;
unit_snprintf(s, 11, d, 'A');
assert(strncmp(s, "4.00 TByte", 11) == 0);
unit_snprintf(s, 11, d, 'a');
assert(strncmp(s, "35.2 Tbit", 11) == 0);
d = 4.0 * 1024 * 1024 * 1024 * 1024 * 1024;
unit_snprintf(s, 11, d, 'A');
assert(strncmp(s, "4096 TByte", 11) == 0);
unit_snprintf(s, 11, d, 'a');
assert(strncmp(s, "36029 Tbit", 11) == 0);
return 0;
}
+46
View File
@@ -0,0 +1,46 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "iperf_util.h"
int
main(int argc, char **argv)
{
char cookie[37];
make_cookie(cookie);
printf("cookie: '%s'\n", cookie);
if (strlen(cookie) != 36)
{
printf("Not 36 characters long!\n");
exit(-1);
}
exit(0);
}
+234
View File
@@ -0,0 +1,234 @@
/*
* iperf, Copyright (c) 2014, 2017, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
/*
* routines related to collection TCP_INFO using getsockopt()
*
* Brian Tierney, ESnet (bltierney@es.net)
*
* Note that this is only really useful on Linux.
* XXX: only standard on linux versions 2.4 and later
#
* FreeBSD has a limited implementation that only includes the following:
* tcpi_snd_ssthresh, tcpi_snd_cwnd, tcpi_rcv_space, tcpi_rtt
* Based on information on http://wiki.freebsd.org/8.0TODO, I dont think this will be
* fixed before v8.1 at the earliest.
*
* OSX has no support.
*
* I think MS Windows does support TCP_INFO, but iperf3 does not currently support Windows.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <errno.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_locale.h"
/*************************************************************/
int
has_tcpinfo(void)
{
#if (defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) \
&& defined(TCP_INFO)
return 1;
#else
return 0;
#endif
}
/*************************************************************/
int
has_tcpinfo_retransmits(void)
{
#if defined(linux) && defined(TCP_MD5SIG)
/* TCP_MD5SIG doesn't actually have anything to do with TCP
** retransmits, it just showed up in the same rev of the header
** file. If it's present then struct tcp_info has the
** tcpi_total_retrans field that we need; if not, not.
*/
return 1;
#else
#if defined(__FreeBSD__) && __FreeBSD_version >= 600000
return 1; /* Should work now */
#elif (defined(__NetBSD__) || defined(__OpenBSD__)) && defined(TCP_INFO)
return 1;
#else
return 0;
#endif
#endif
}
/*************************************************************/
void
save_tcpinfo(struct iperf_stream *sp, struct iperf_interval_results *irp)
{
#if (defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) && \
defined(TCP_INFO)
socklen_t tcp_info_length = sizeof(struct tcp_info);
if (getsockopt(sp->socket, IPPROTO_TCP, TCP_INFO, (void *)&irp->tcpInfo, &tcp_info_length) < 0)
iperf_err(sp->test, "getsockopt - %s", strerror(errno));
if (sp->test->debug) {
printf("tcpi_snd_cwnd %u tcpi_snd_mss %u tcpi_rtt %u\n",
irp->tcpInfo.tcpi_snd_cwnd, irp->tcpInfo.tcpi_snd_mss,
irp->tcpInfo.tcpi_rtt);
}
#endif
}
/*************************************************************/
long
get_total_retransmits(struct iperf_interval_results *irp)
{
#if defined(linux) && defined(TCP_MD5SIG)
return irp->tcpInfo.tcpi_total_retrans;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 600000
return irp->tcpInfo.tcpi_snd_rexmitpack;
#elif (defined(__NetBSD__) || defined(__OpenBSD__)) && defined(TCP_INFO)
return irp->tcpInfo.tcpi_snd_rexmitpack;
#else
return -1;
#endif
}
/*************************************************************/
/*
* Return snd_cwnd in octets.
*/
long
get_snd_cwnd(struct iperf_interval_results *irp)
{
#if defined(linux) && defined(TCP_MD5SIG)
return (long)irp->tcpInfo.tcpi_snd_cwnd * irp->tcpInfo.tcpi_snd_mss;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 600000
return irp->tcpInfo.tcpi_snd_cwnd;
#elif defined(__NetBSD__) && defined(TCP_INFO)
return (long)irp->tcpInfo.tcpi_snd_cwnd * irp->tcpInfo.tcpi_snd_mss;
#elif defined(__OpenBSD__) && defined(TCP_INFO)
return irp->tcpInfo.tcpi_snd_cwnd;
#else
return -1;
#endif
}
/*************************************************************/
/*
* Return snd_wnd in octets.
*/
long
get_snd_wnd(struct iperf_interval_results *irp)
{
#if !defined(HAVE_TCP_INFO_SND_WND)
return -1;
#elif defined(linux) && defined(TCP_MD5SIG)
return irp->tcpInfo.tcpi_snd_wnd;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 600000
return irp->tcpInfo.tcpi_snd_wnd;
#elif defined(__NetBSD__) && defined(TCP_INFO)
return (long)irp->tcpInfo.tcpi_snd_wnd * irp->tcpInfo.tcpi_snd_mss;
#elif defined(__OpenBSD__) && defined(TCP_INFO)
return irp->tcpInfo.tcpi_snd_wnd;
#else
return -1;
#endif
}
/*************************************************************/
/*
* Return rtt in usec.
*/
long
get_rtt(struct iperf_interval_results *irp)
{
#if defined(linux) && defined(TCP_MD5SIG)
return irp->tcpInfo.tcpi_rtt;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 600000
return irp->tcpInfo.tcpi_rtt;
#elif (defined(__NetBSD__) || defined(__OpenBSD__)) && defined(TCP_INFO)
return irp->tcpInfo.tcpi_rtt;
#else
return -1;
#endif
}
/*************************************************************/
/*
* Return rttvar in usec.
*/
long
get_rttvar(struct iperf_interval_results *irp)
{
#if defined(linux) && defined(TCP_MD5SIG)
return irp->tcpInfo.tcpi_rttvar;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 600000
return irp->tcpInfo.tcpi_rttvar;
#elif (defined(__NetBSD__) || defined(__OpenBSD__)) && defined(TCP_INFO)
return irp->tcpInfo.tcpi_rttvar;
#else
return -1;
#endif
}
/*************************************************************/
/*
* Return PMTU in bytes.
*/
long
get_pmtu(struct iperf_interval_results *irp)
{
#if defined(linux) && defined(TCP_MD5SIG)
return irp->tcpInfo.tcpi_pmtu;
#else
return -1;
#endif
}
/*************************************************************/
void
build_tcpinfo_message(struct iperf_interval_results *r, char *message)
{
#if defined(linux) && defined(TCP_INFO)
sprintf(message, report_tcpInfo, r->tcpInfo.tcpi_snd_cwnd, r->tcpInfo.tcpi_snd_ssthresh,
r->tcpInfo.tcpi_rcv_ssthresh, r->tcpInfo.tcpi_unacked, r->tcpInfo.tcpi_sacked,
r->tcpInfo.tcpi_lost, r->tcpInfo.tcpi_retrans, r->tcpInfo.tcpi_fackets,
r->tcpInfo.tcpi_rtt, r->tcpInfo.tcpi_reordering);
#endif
#if (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) && defined(TCP_INFO)
sprintf(message, report_tcpInfo, r->tcpInfo.tcpi_snd_cwnd,
r->tcpInfo.tcpi_rcv_space, r->tcpInfo.tcpi_snd_ssthresh, r->tcpInfo.tcpi_rtt);
#endif
}
+245
View File
@@ -0,0 +1,245 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*
* Based on timers.c by Jef Poskanzer. Used with permission.
*/
#include <sys/types.h>
#include <stdlib.h>
#include "timer.h"
#include "iperf_time.h"
static Timer* timers = NULL;
static Timer* free_timers = NULL;
TimerClientData JunkClientData;
/* This is an efficiency tweak. All the routines that need to know the
** current time get passed a pointer to a struct iperf_time. If it's non-NULL
** it gets used, otherwise we do our own iperf_time_now() to fill it in.
** This lets the caller avoid extraneous iperf_time_now()s when efficiency
** is needed, and not bother with the extra code when efficiency doesn't
** matter too much.
*/
static void
getnow( struct iperf_time* nowP, struct iperf_time* nowP2 )
{
if ( nowP != NULL )
*nowP2 = *nowP;
else
iperf_time_now(nowP2);
}
static void
list_add( Timer* t )
{
Timer* t2;
Timer* t2prev;
if ( timers == NULL ) {
/* The list is empty. */
timers = t;
t->prev = t->next = NULL;
} else {
if (iperf_time_compare(&t->time, &timers->time) < 0) {
/* The new timer goes at the head of the list. */
t->prev = NULL;
t->next = timers;
timers->prev = t;
timers = t;
} else {
/* Walk the list to find the insertion point. */
for ( t2prev = timers, t2 = timers->next; t2 != NULL;
t2prev = t2, t2 = t2->next ) {
if (iperf_time_compare(&t->time, &t2->time) < 0) {
/* Found it. */
t2prev->next = t;
t->prev = t2prev;
t->next = t2;
t2->prev = t;
return;
}
}
/* Oops, got to the end of the list. Add to tail. */
t2prev->next = t;
t->prev = t2prev;
t->next = NULL;
}
}
}
static void
list_remove( Timer* t )
{
if ( t->prev == NULL )
timers = t->next;
else
t->prev->next = t->next;
if ( t->next != NULL )
t->next->prev = t->prev;
}
static void
list_resort( Timer* t )
{
/* Remove the timer from the list. */
list_remove( t );
/* And add it back in, sorted correctly. */
list_add( t );
}
Timer*
tmr_create(
struct iperf_time* nowP, TimerProc* timer_proc, TimerClientData client_data,
int64_t usecs, int periodic )
{
struct iperf_time now;
Timer* t;
getnow( nowP, &now );
if ( free_timers != NULL ) {
t = free_timers;
free_timers = t->next;
} else {
t = (Timer*) malloc( sizeof(Timer) );
if ( t == NULL )
return NULL;
}
t->timer_proc = timer_proc;
t->client_data = client_data;
t->usecs = usecs;
t->periodic = periodic;
t->time = now;
iperf_time_add_usecs(&t->time, usecs);
/* Add the new timer to the active list. */
list_add( t );
return t;
}
struct timeval*
tmr_timeout( struct iperf_time* nowP )
{
struct iperf_time now, diff;
int64_t usecs;
int past;
static struct timeval timeout;
getnow( nowP, &now );
/* Since the list is sorted, we only need to look at the first timer. */
if ( timers == NULL )
return NULL;
past = iperf_time_diff(&timers->time, &now, &diff);
if (past)
usecs = 0;
else
usecs = iperf_time_in_usecs(&diff);
timeout.tv_sec = usecs / 1000000LL;
timeout.tv_usec = usecs % 1000000LL;
return &timeout;
}
void
tmr_run( struct iperf_time* nowP )
{
struct iperf_time now;
Timer* t;
Timer* next;
getnow( nowP, &now );
for ( t = timers; t != NULL; t = next ) {
next = t->next;
/* Since the list is sorted, as soon as we find a timer
** that isn't ready yet, we are done.
*/
if (iperf_time_compare(&t->time, &now) > 0)
break;
(t->timer_proc)( t->client_data, &now );
if ( t->periodic ) {
/* Reschedule. */
iperf_time_add_usecs(&t->time, t->usecs);
list_resort( t );
} else
tmr_cancel( t );
}
}
void
tmr_reset( struct iperf_time* nowP, Timer* t )
{
struct iperf_time now;
getnow( nowP, &now );
t->time = now;
iperf_time_add_usecs( &t->time, t->usecs );
list_resort( t );
}
void
tmr_cancel( Timer* t )
{
/* Remove it from the active list. */
list_remove( t );
/* And put it on the free list. */
t->next = free_timers;
free_timers = t;
t->prev = NULL;
}
void
tmr_cleanup( void )
{
Timer* t;
while ( free_timers != NULL ) {
t = free_timers;
free_timers = t->next;
free( (void*) t );
}
}
void
tmr_destroy( void )
{
while ( timers != NULL )
tmr_cancel( timers );
tmr_cleanup();
}
+100
View File
@@ -0,0 +1,100 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*
* Based on timers.h by Jef Poskanzer. Used with permission.
*/
#ifndef __TIMER_H
#define __TIMER_H
#include <time.h>
#include <sys/time.h>
#include "iperf_time.h"
/* TimerClientData is an opaque value that tags along with a timer. The
** client can use it for whatever, and it gets passed to the callback when
** the timer triggers.
*/
typedef union
{
void* p;
int i;
long l;
} TimerClientData;
extern TimerClientData JunkClientData; /* for use when you don't care */
/* The TimerProc gets called when the timer expires. It gets passed
** the TimerClientData associated with the timer, and a iperf_time in case
** it wants to schedule another timer.
*/
typedef void TimerProc( TimerClientData client_data, struct iperf_time* nowP );
/* The Timer struct. */
typedef struct TimerStruct
{
TimerProc* timer_proc;
TimerClientData client_data;
int64_t usecs;
int periodic;
struct iperf_time time;
struct TimerStruct* prev;
struct TimerStruct* next;
int hash;
} Timer;
/* Set up a timer, either periodic or one-shot. Returns (Timer*) 0 on errors. */
extern Timer* tmr_create(
struct iperf_time* nowP, TimerProc* timer_proc, TimerClientData client_data,
int64_t usecs, int periodic );
/* Returns a timeout indicating how long until the next timer triggers. You
** can just put the call to this routine right in your select(). Returns
** (struct timeval*) 0 if no timers are pending.
*/
extern struct timeval* tmr_timeout( struct iperf_time* nowP ) /* __attribute__((hot)) */;
/* Run the list of timers. Your main program needs to call this every so often,
** or as indicated by tmr_timeout().
*/
extern void tmr_run( struct iperf_time* nowP ) /* __attribute__((hot)) */;
/* Reset the clock on a timer, to current time plus the original timeout. */
extern void tmr_reset( struct iperf_time* nowP, Timer* timer );
/* Deschedule a timer. Note that non-periodic timers are automatically
** descheduled when they run, so you don't have to call this on them.
*/
extern void tmr_cancel( Timer* timer );
/* Clean up the timers package, freeing any unused storage. */
extern void tmr_cleanup( void );
/* Cancel all timers and free storage, usually in preparation for exiting. */
extern void tmr_destroy( void );
#endif /* __TIMER_H */
+353
View File
@@ -0,0 +1,353 @@
/*---------------------------------------------------------------
* Copyright (c) 1999,2000,2001,2002,2003
* The Board of Trustees of the University of Illinois
* All Rights Reserved.
*---------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software (Iperf) and associated
* documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
*
* Redistributions of source code must retain the above
* copyright notice, this list of conditions and
* the following disclaimers.
*
*
* Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimers in the documentation and/or other materials
* provided with the distribution.
*
*
* Neither the names of the University of Illinois, NCSA,
* nor the names of its contributors may be used to endorse
* or promote products derived from this Software without
* specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ________________________________________________________________
* National Laboratory for Applied Network Research
* National Center for Supercomputing Applications
* University of Illinois at Urbana-Champaign
* http://www.ncsa.uiuc.edu
* ________________________________________________________________
*
* stdio.c
* by Mark Gates <mgates@nlanr.net>
* and Ajay Tirumalla <tirumala@ncsa.uiuc.edu>
* -------------------------------------------------------------------
* input and output numbers, converting with kilo, mega, giga, tera
* ------------------------------------------------------------------- */
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include "iperf.h"
#ifdef __cplusplus
extern "C"
{
#endif
const double KILO_UNIT = 1024.0;
const double MEGA_UNIT = 1024.0 * 1024.0;
const double GIGA_UNIT = 1024.0 * 1024.0 * 1024.0;
const double TERA_UNIT = 1024.0 * 1024.0 * 1024.0 * 1024.0;
const double KILO_RATE_UNIT = 1000.0;
const double MEGA_RATE_UNIT = 1000.0 * 1000.0;
const double GIGA_RATE_UNIT = 1000.0 * 1000.0 * 1000.0;
const double TERA_RATE_UNIT = 1000.0 * 1000.0 * 1000.0 * 1000.0;
/* -------------------------------------------------------------------
* unit_atof
*
* Given a string of form #x where # is a number and x is a format
* character listed below, this returns the interpreted integer.
* Gg, Mm, Kk are giga, mega, kilo respectively
* ------------------------------------------------------------------- */
double unit_atof(const char *s)
{
double n;
char suffix = '\0';
assert(s != NULL);
/* scan the number and any suffices */
sscanf(s, "%lf%c", &n, &suffix);
/* convert according to [Tt Gg Mm Kk] */
switch (suffix)
{
case 't': case 'T':
n *= TERA_UNIT;
break;
case 'g': case 'G':
n *= GIGA_UNIT;
break;
case 'm': case 'M':
n *= MEGA_UNIT;
break;
case 'k': case 'K':
n *= KILO_UNIT;
break;
default:
break;
}
return n;
} /* end unit_atof */
/* -------------------------------------------------------------------
* unit_atof_rate
*
* Similar to unit_atof, but uses 10-based rather than 2-based
* suffixes.
* ------------------------------------------------------------------- */
double unit_atof_rate(const char *s)
{
double n;
char suffix = '\0';
assert(s != NULL);
/* scan the number and any suffices */
sscanf(s, "%lf%c", &n, &suffix);
/* convert according to [Tt Gg Mm Kk] */
switch (suffix)
{
case 't': case 'T':
n *= TERA_RATE_UNIT;
break;
case 'g': case 'G':
n *= GIGA_RATE_UNIT;
break;
case 'm': case 'M':
n *= MEGA_RATE_UNIT;
break;
case 'k': case 'K':
n *= KILO_RATE_UNIT;
break;
default:
break;
}
return n;
} /* end unit_atof_rate */
/* -------------------------------------------------------------------
* unit_atoi
*
* Given a string of form #x where # is a number and x is a format
* character listed below, this returns the interpreted integer.
* Tt, Gg, Mm, Kk are tera, giga, mega, kilo respectively
* ------------------------------------------------------------------- */
iperf_size_t unit_atoi(const char *s)
{
double n;
char suffix = '\0';
assert(s != NULL);
/* scan the number and any suffices */
sscanf(s, "%lf%c", &n, &suffix);
/* convert according to [Tt Gg Mm Kk] */
switch (suffix)
{
case 't': case 'T':
n *= TERA_UNIT;
break;
case 'g': case 'G':
n *= GIGA_UNIT;
break;
case 'm': case 'M':
n *= MEGA_UNIT;
break;
case 'k': case 'K':
n *= KILO_UNIT;
break;
default:
break;
}
return (iperf_size_t) n;
} /* end unit_atof */
/* -------------------------------------------------------------------
* constants for byte_printf
* ------------------------------------------------------------------- */
/* used as indices into conversion_bytes[], label_byte[], and label_bit[] */
enum
{
UNIT_CONV,
KILO_CONV,
MEGA_CONV,
GIGA_CONV,
TERA_CONV
};
/* factor to multiply the number by */
const double conversion_bytes[] =
{
1.0, /* unit */
1.0 / 1024, /* kilo */
1.0 / 1024 / 1024, /* mega */
1.0 / 1024 / 1024 / 1024, /* giga */
1.0 / 1024 / 1024 / 1024 / 1024 /* tera */
};
/* factor to multiply the number by for bits*/
const double conversion_bits[] =
{
1.0, /* unit */
1.0 / 1000, /* kilo */
1.0 / 1000 / 1000, /* mega */
1.0 / 1000 / 1000 / 1000, /* giga */
1.0 / 1000 / 1000 / 1000 / 1000 /* tera */
};
/* labels for Byte formats [KMGT] */
const char *label_byte[] =
{
"Byte",
"KByte",
"MByte",
"GByte",
"TByte"
};
/* labels for bit formats [kmgt] */
const char *label_bit[] =
{
"bit",
"Kbit",
"Mbit",
"Gbit",
"Tbit"
};
/* -------------------------------------------------------------------
* unit_snprintf
*
* Given a number in bytes and a format, converts the number and
* prints it out with a bits or bytes label.
* B, K, M, G, A for Byte, Kbyte, Mbyte, Gbyte, adaptive byte
* b, k, m, g, a for bit, Kbit, Mbit, Gbit, adaptive bit
* adaptive picks the "best" one based on the number.
* s should be at least 11 chars long
* (4 digits + space + 5 chars max + null)
* ------------------------------------------------------------------- */
void unit_snprintf(char *s, int inLen,
double inNum, char inFormat)
{
int conv;
const char *suffix;
const char *format;
/* convert to bits for [bkmga] */
if (!isupper((int) inFormat))
{
inNum *= 8;
}
switch (toupper((u_char)inFormat))
{
case 'B':
conv = UNIT_CONV;
break;
case 'K':
conv = KILO_CONV;
break;
case 'M':
conv = MEGA_CONV;
break;
case 'G':
conv = GIGA_CONV;
break;
case 'T':
conv = TERA_CONV;
break;
default:
case 'A':
{
double tmpNum = inNum;
conv = UNIT_CONV;
if (isupper((int) inFormat))
{
while (tmpNum >= 1024.0 && conv < TERA_CONV)
{
tmpNum /= 1024.0;
conv++;
}
} else
{
while (tmpNum >= 1000.0 && conv < TERA_CONV)
{
tmpNum /= 1000.0;
conv++;
}
}
break;
}
}
if (!isupper((int) inFormat))
{
inNum *= conversion_bits[conv];
suffix = label_bit[conv];
} else
{
inNum *= conversion_bytes[conv];
suffix = label_byte[conv];
}
/* print such that we always fit in 4 places */
if (inNum < 9.995)
{ /* 9.995 would be rounded to 10.0 */
format = "%4.2f %s";/* #.## */
} else if (inNum < 99.95)
{ /* 99.95 would be rounded to 100 */
format = "%4.1f %s";/* ##.# */
} else if (inNum < 999.5)
{ /* 999.5 would be rounded to 1000 */
format = "%4.0f %s";/* ### */
} else
{ /* 1000-1024 fits in 4 places If not using
* Adaptive sizes then this code will not
* control spaces */
format = "%4.0f %s";/* #### */
}
snprintf(s, inLen, format, inNum, suffix);
} /* end unit_snprintf */
#ifdef __cplusplus
} /* end extern "C" */
#endif
+34
View File
@@ -0,0 +1,34 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
enum {
UNIT_LEN = 32
};
double unit_atof( const char *s );
double unit_atof_rate( const char *s );
iperf_size_t unit_atoi( const char *s );
void unit_snprintf( char *s, int inLen, double inNum, char inFormat );
+27
View File
@@ -0,0 +1,27 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#define IPERF_VERSION "3.19"
@@ -0,0 +1,27 @@
/*
* iperf, Copyright (c) 2014, The Regents of the University of
* California, through Lawrence Berkeley National Laboratory (subject
* to receipt of any required approvals from the U.S. Dept. of
* Energy). All rights reserved.
*
* If you have questions about your rights to use or distribute this
* software, please contact Berkeley Lab's Technology Transfer
* Department at TTD@lbl.gov.
*
* NOTICE. This software is owned by the U.S. Department of Energy.
* As such, the U.S. Government has been granted for itself and others
* acting on its behalf a paid-up, nonexclusive, irrevocable,
* worldwide license in the Software to reproduce, prepare derivative
* works, and perform publicly and display publicly. Beginning five
* (5) years after the date permission to assert copyright is obtained
* from the U.S. Department of Energy, and subject to any subsequent
* five (5) year renewals, the U.S. Government is granted for itself
* and others acting on its behalf a paid-up, nonexclusive,
* irrevocable, worldwide license in the Software to reproduce,
* prepare derivative works, distribute copies to the public, perform
* publicly and display publicly, and to permit others to do so.
*
* This code is distributed under a BSD style license, see the LICENSE
* file for complete information.
*/
#define IPERF_VERSION "@PACKAGE_VERSION@"
+184
View File
@@ -0,0 +1,184 @@
/* src/iperf_config.h.in. Generated for Android JNI build. */
/* Can bind to device via SO_BINDTODEVICE or IP_BOUND_IF. */
/* #undef CAN_BIND_TO_DEVICE */
/* Define to 1 if you have the 'clock_gettime' function. */
#define HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the 'clock_nanosleep' function. */
/* #undef HAVE_CLOCK_NANOSLEEP */
/* Define to 1 if you have the 'cpuset_setaffinity' function. */
/* #undef HAVE_CPUSET_SETAFFINITY */
/* Have CPU affinity support. */
#define HAVE_CPU_AFFINITY 1
/* Define to 1 if you have the 'daemon' function. */
/* #undef HAVE_DAEMON */
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Have IP_MTU_DISCOVER/IP_DONTFRAG/IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_DONT_FRAGMENT */
/* Define to 1 if you have the <endian.h> header file. */
#define HAVE_ENDIAN_H 1
/* Have IPv6 flowlabel support. */
/* #undef HAVE_FLOWLABEL */
/* Define to 1 if you have the 'getline' function. */
#define HAVE_GETLINE 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Have MPTCP protocol. */
/* #undef HAVE_IPPROTO_MPTCP */
/* Have IP_BOUND_IF sockopt. */
/* #undef HAVE_IP_BOUND_IF */
/* Have IP_DONTFRAG sockopt. */
/* #undef HAVE_IP_DONTFRAG */
/* Have IP_DONTFRAGMENT sockopt. */
/* #undef HAVE_IP_DONTFRAGMENT */
/* Have IP_MTU_DISCOVER sockopt. */
/* #undef HAVE_IP_MTU_DISCOVER */
/* Define to 1 if you have the <linux/tcp.h> header file. */
/* #undef HAVE_LINUX_TCP_H */
/* Have MSG_TRUNC recv option. */
#define HAVE_MSG_TRUNC 1
/* Define to 1 if you have the 'nanosleep' function. */
#define HAVE_NANOSLEEP 1
/* Define to 1 if you have the <netinet/sctp.h> header file. */
/* #undef HAVE_NETINET_SCTP_H */
/* Define to 1 if you have the <poll.h> header file. */
#define HAVE_POLL_H 1
/* Define if you have POSIX threads libraries and header files. */
#define HAVE_PTHREAD 1
/* Have PTHREAD_PRIO_INHERIT. */
/* #undef HAVE_PTHREAD_PRIO_INHERIT */
/* Define to 1 if you have the 'sched_setaffinity' function. */
/* #undef HAVE_SCHED_SETAFFINITY */
/* Have SCTP support. */
/* #undef HAVE_SCTP_H */
/* Define to 1 if you have the 'sendfile' function. */
/* #undef HAVE_SENDFILE */
/* Define to 1 if you have the 'SetProcessAffinityMask' function. */
/* #undef HAVE_SETPROCESSAFFINITYMASK */
/* Have SHUT_WR socket shutdown option. */
#define HAVE_SOCKET_SHUTDOWN_SHUT_WR 1
/* Have SO_BINDTODEVICE sockopt. */
/* #undef HAVE_SO_BINDTODEVICE */
/* Have SO_MAX_PACING_RATE sockopt. */
/* #undef HAVE_SO_MAX_PACING_RATE */
/* OpenSSL Is Available */
/* #undef HAVE_SSL */
/* Define to 1 if you have the <stdatomic.h> header file. */
#define HAVE_STDATOMIC_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdio.h> header file. */
#define HAVE_STDIO_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if the system has the type 'struct sctp_assoc_value'. */
/* #undef HAVE_STRUCT_SCTP_ASSOC_VALUE */
/* Define to 1 if you have the <sys/endian.h> header file. */
/* #undef HAVE_SYS_ENDIAN_H */
/* Define to 1 if you have the <sys/socket.h> header file. */
#define HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Have TCP_CONGESTION sockopt. */
#define HAVE_TCP_CONGESTION 1
/* Have TCP_KEEPIDLE sockopt. */
#define HAVE_TCP_KEEPALIVE 1
/* Have TCP_USER_TIMEOUT sockopt. */
#define HAVE_TCP_USER_TIMEOUT 1
/* Have UDP_GRO sockopt. */
/* #undef HAVE_UDP_GRO */
/* Have UDP_SEGMENT sockopt. */
/* #undef HAVE_UDP_SEGMENT */
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#define LT_OBJDIR ".libs/"
/* Name of package */
#define PACKAGE "iperf"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "iperf-devel@lists.es.net"
/* Define to the full name of this package. */
#define PACKAGE_NAME "iperf"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "iperf 3.19"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "iperf"
/* Define to the home page for this package. */
#define PACKAGE_URL "http://iperf.fr/"
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.19"
/* Define to necessary symbol if this constant uses a non-standard name on your system. */
#define PTHREAD_CREATE_JOINABLE 2
/* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */
#define STDC_HEADERS 1
/* Version number of package */
#define VERSION "3.19"
/* Define to empty if 'const' does not conform to ANSI C. */
/* #undef const */
+502
View File
@@ -0,0 +1,502 @@
/*
* iperf JNI bridge for Android.
*
* This file exposes iperf3's API to Java/Kotlin via JNI, enabling
* the Flutter app to run iperf3 tests natively inside the app process
* (no Process.start needed, no SELinux restrictions).
*
* OUTPUT NOTE:
* The test runs in --json-stream mode with a json_callback installed, so
* iperf3 hands each event ("start", "interval", "end", "error") straight to
* us as a JSON string and never touches test->outfile. That is what the hook
* exists for -- iperf.h calls it "allow user apps to receive the JSON
* strings, instead of writing them to the output file".
*
* An earlier version piped test->outfile to a reader thread instead. That
* required closing the pipe's write end before joining the reader (which
* blocks in fgets() until EOF) and single ownership of the shared state
* struct. Both were gotten wrong and deadlocked every test. Reading events
* from the callback removes the pipe, the reader thread, and that whole class
* of ordering bug.
*
* THREADING NOTE:
* runIperfLive() BLOCKS for the whole duration of the test. Kotlin must
* call it from a worker thread, never from the Flutter platform thread.
* json_callback fires on whichever thread iperf3 reports from, so it resolves
* a JNIEnv for the calling thread rather than reusing a stored one.
*
* SIGNAL HANDLING NOTE:
* iperf3's run() function (called from iperf_run_client()) uses
* setjmp/longjmp for graceful signal handling. The JNI bridge must
* replicate this setup — otherwise iperf_catch_sigend() installs
* handlers that longjmp to an unset buffer, causing undefined
* behaviour (typically a SIGSEGV or hang). Handlers are restored to
* SIG_DFL once the test finishes so a later signal cannot longjmp into
* a stack frame that no longer exists.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <android/log.h>
#include <jni.h>
#include <pthread.h>
#include <sys/socket.h>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_time.h"
/* Android logging macros */
#define TAG "iperf3-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
/* ------------------------------------------------------------------ */
/* Global state for controlling the iperf test. */
/* */
/* g_test is written by the worker thread running the test and read by */
/* the platform thread in forceStopIperfTest()/isTestRunning(), so all */
/* access is serialised through g_test_mutex. The mutex is never held */
/* across iperf_run_client() — only around pointer publish/retire and */
/* the (short) stop request. */
/* ------------------------------------------------------------------ */
static struct iperf_test *g_test = NULL;
static pthread_mutex_t g_test_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile int g_stop = 0;
static char g_tmp_template[1024] = "/data/local/tmp/iperf3.XXXXXX";
/* Per-call state, reachable from the json_callback.
* Callbacks are invoked via CallVoidMethod, NOT as function pointers. */
struct iperf_run_state {
jobject callback_ref; /* Global ref to the callback object */
jmethodID mid_output; /* Method ID for onOutput(String) */
jmethodID mid_error; /* Method ID for onError(String) */
jmethodID mid_complete; /* Method ID for onComplete() */
JavaVM *jvm;
};
/* iperf3's json_callback signature carries no user-data pointer, so the
* current run's state has to be reachable from a global. Only one test runs
* at a time (enforced in runIperfLive), and this is published before
* iperf_run_client() and cleared after it returns. */
static struct iperf_run_state *g_current_state = NULL;
/* Resolve a method ID, clearing the exception a failed lookup leaves behind.
*
* GetMethodID raises NoSuchMethodError on failure, and any further JNI call
* made while an exception is pending aborts the process. Returning NULL and
* clearing lets the caller fail on its own terms. */
static jmethodID resolve_method(JNIEnv *env, jclass cls,
const char *name, const char *sig) {
jmethodID mid = (*env)->GetMethodID(env, cls, name, sig);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
return NULL;
}
return mid;
}
/* Helper: invoke the output callback via CallVoidMethod */
static void signal_output(JNIEnv *env, struct iperf_run_state *state,
const char *msg) {
if (!env || !state || !msg || !msg[0] || !state->mid_output) return;
jstring jline = (*env)->NewStringUTF(env, msg);
if (jline) {
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_output, jline);
(*env)->DeleteLocalRef(env, jline);
}
}
/* Helper: invoke the error callback via CallVoidMethod */
static void signal_error(JNIEnv *env, struct iperf_run_state *state,
const char *msg) {
if (!env || !state || !msg || !state->mid_error) return;
jstring jmsg = (*env)->NewStringUTF(env, msg);
if (jmsg) {
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_error, jmsg);
(*env)->DeleteLocalRef(env, jmsg);
}
}
/* Helper: invoke the complete callback via CallVoidMethod */
static void signal_complete(JNIEnv *env, struct iperf_run_state *state) {
if (!env || !state || !state->mid_complete) return;
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_complete);
}
/* ------------------------------------------------------------------ */
/* json_callback — receives one JSON event string per report from */
/* iperf3 and forwards it to Java verbatim. Parsing happens in Dart. */
/* */
/* Runs on iperf3's reporting thread. In the client that is the thread */
/* that called iperf_run_client() (already attached, since Kotlin's */
/* executor thread is a Java thread), but GetEnv is checked rather */
/* than assumed, with an attach as fallback. */
/* ------------------------------------------------------------------ */
static void json_event_callback(struct iperf_test *test, char *json) {
struct iperf_run_state *state = g_current_state;
if (!state || !json) return;
JNIEnv *env = NULL;
int attached = 0;
if ((*state->jvm)->GetEnv(state->jvm, (void **) &env, JNI_VERSION_1_6)
!= JNI_OK) {
if ((*state->jvm)->AttachCurrentThread(state->jvm, &env, NULL)
!= JNI_OK) {
LOGE("json_event_callback: could not reach a JNIEnv");
return;
}
attached = 1;
}
signal_output(env, state, json);
if (attached) {
(*state->jvm)->DetachCurrentThread(state->jvm);
}
}
/* ------------------------------------------------------------------ */
/* Build a native argv from a Java String[], prepending "iperf3". */
/* */
/* iperf_parse_arguments() expects argv[0] to be the program name; */
/* passing the Dart args directly would make argv[0] "-c", which */
/* iperf3 misreads as the program name rather than the client flag. */
/* ------------------------------------------------------------------ */
static char **build_argv(JNIEnv *env, jobjectArray str_array, int *out_argc) {
int user_argc = (*env)->GetArrayLength(env, str_array);
if (user_argc < 0) user_argc = 0;
int argc = user_argc + 1; /* +1 for argv[0] */
char **argv = malloc((argc + 1) * sizeof(char *));
if (!argv) {
*out_argc = 0;
return NULL;
}
argv[0] = strdup("iperf3");
for (int i = 0; i < user_argc; i++) {
jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, str_array, i);
const char *cstr = jstr ? (*env)->GetStringUTFChars(env, jstr, NULL) : NULL;
if (cstr) {
argv[i + 1] = strdup(cstr);
(*env)->ReleaseStringUTFChars(env, jstr, cstr);
} else {
argv[i + 1] = strdup("");
}
if (jstr) (*env)->DeleteLocalRef(env, jstr);
}
argv[argc] = NULL; /* NULL-terminated for iperf */
*out_argc = argc;
return argv;
}
/* Free an argv array returned by build_argv() */
static void free_argv(char **argv, int argc) {
if (!argv) return;
for (int i = 0; i < argc; i++) {
free(argv[i]);
}
free(argv);
}
/* Retire the global test pointer and free it, under the mutex. */
static void retire_test(void) {
pthread_mutex_lock(&g_test_mutex);
struct iperf_test *test = g_test;
g_test = NULL;
if (test) {
iperf_free_test(test);
}
pthread_mutex_unlock(&g_test_mutex);
}
/* ------------------------------------------------------------------ */
/* Internal: clean up after iperf_run_client returns and report the */
/* outcome to Java. Called from both the normal and the longjmp path. */
/* Frees `state`; do not touch it afterwards. */
/* ------------------------------------------------------------------ */
static void iperf_cleanup_and_signal(JNIEnv *env,
struct iperf_run_state *state,
char **argv, int argc,
int ret, int stopped) {
/* No more events can be delivered once the run is over. */
g_current_state = NULL;
/* Restore default signal handlers before g_sigend_jmp_buf goes stale. */
iperf_catch_sigend(SIG_DFL);
retire_test();
free_argv(argv, argc);
/* A user-requested stop shuts down the control socket, so
* iperf_run_client() returns -1 with "Broken pipe" — expected, not a
* failure. Check g_stop so that path is reported as a stop, not an error. */
int user_stopped = stopped || g_stop;
g_stop = 0;
/* Signal result to Java */
if (user_stopped) {
signal_output(env, state, "{\"event\":\"jni_stopped\",\"data\":{}}");
} else if (ret < 0) {
char errbuf[256];
snprintf(errbuf, sizeof(errbuf), "%s", iperf_strerror(i_errno));
signal_error(env, state, errbuf);
}
/* Signal completion to Java */
signal_complete(env, state);
/* Free global ref and state struct */
(*env)->DeleteGlobalRef(env, state->callback_ref);
free(state);
}
/* ------------------------------------------------------------------ */
/* Per-call signal handling state. */
/* Must be static so it survives across the longjmp back to setjmp. */
/* ------------------------------------------------------------------ */
static jmp_buf g_sigend_jmp_buf;
static volatile int g_signed_sig = 0;
/* Signal handler — mirrors main.c::sigend_handler */
static void sigend_handler(int sig) {
g_signed_sig = sig;
longjmp(g_sigend_jmp_buf, 1);
}
/* ------------------------------------------------------------------ */
/* JNI: Start an iperf3 test with the given arguments. */
/* Signature: void runIperfLive(String[] args, IperfCallback cb) */
/* */
/* BLOCKS until the test finishes — call from a worker thread. */
/* ------------------------------------------------------------------ */
JNIEXPORT void JNICALL
Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
JNIEnv *env, jobject thiz,
jobjectArray arguments, jobject callback) {
/* Resolve callback method IDs up front so failures can be reported. */
jclass callback_cls = (*env)->GetObjectClass(env, callback);
jmethodID mid_output = resolve_method(
env, callback_cls, "onOutput", "(Ljava/lang/String;)V");
jmethodID mid_error = resolve_method(
env, callback_cls, "onError", "(Ljava/lang/String;)V");
jmethodID mid_complete = resolve_method(
env, callback_cls, "onComplete", "()V");
/* If the names did not resolve there is no way to report anything: the
* reporting path is the thing that is missing. Bail out loudly instead.
*
* In practice this means R8 renamed the callback in a release build and
* android/app/proguard-rules.pro has stopped covering it. */
if (!mid_output || !mid_error || !mid_complete) {
LOGE("Callback methods did not resolve (onOutput=%p onError=%p "
"onComplete=%p) — check the -keep rules for IperfCallback",
mid_output, mid_error, mid_complete);
return;
}
/* Report a startup failure directly on the callback object and finish,
* so Dart's completer always resolves instead of waiting for a timeout. */
#define FAIL_EARLY(msg) do { \
LOGE("%s", msg); \
if (mid_error) { \
jstring jm = (*env)->NewStringUTF(env, msg); \
if (jm) { \
(*env)->CallVoidMethod(env, callback, mid_error, jm); \
(*env)->DeleteLocalRef(env, jm); \
} \
} \
if (mid_complete) (*env)->CallVoidMethod(env, callback, mid_complete); \
} while (0)
/* Refuse to start a second concurrent test — the globals above hold
* exactly one test's worth of state. */
pthread_mutex_lock(&g_test_mutex);
int busy = (g_test != NULL);
pthread_mutex_unlock(&g_test_mutex);
if (busy) {
FAIL_EARLY("A test is already running.");
return;
}
/* Create the iperf test object */
struct iperf_test *test = iperf_new_test();
if (!test) {
FAIL_EARLY("Failed to create iperf test");
return;
}
iperf_defaults(test);
pthread_mutex_lock(&g_test_mutex);
g_test = test;
pthread_mutex_unlock(&g_test_mutex);
int total_argc = 0;
char **argv = build_argv(env, arguments, &total_argc);
if (!argv) {
retire_test();
FAIL_EARLY("Failed to allocate argv");
return;
}
/* Set a reasonable connect timeout (30s) to avoid hanging on DNS
* resolution. Default iperf3 connect timeout is 120s, far too long for a
* phone UI. Set before parsing so an explicit --connect-timeout wins. */
test->settings->connect_timeout = 30000;
/* Parse arguments */
if (iperf_parse_arguments(test, total_argc, argv) < 0) {
char errbuf[256];
snprintf(errbuf, sizeof(errbuf),
"Argument parse error: %s", iperf_strerror(i_errno));
free_argv(argv, total_argc);
retire_test();
FAIL_EARLY(errbuf);
return;
}
/* Allocate per-call state */
struct iperf_run_state *state =
calloc(1, sizeof(struct iperf_run_state));
if (!state) {
free_argv(argv, total_argc);
retire_test();
FAIL_EARLY("Failed to allocate run state");
return;
}
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
state->mid_output = mid_output;
state->mid_error = mid_error;
state->mid_complete = mid_complete;
state->callback_ref = (*env)->NewGlobalRef(env, callback);
state->jvm = jvm;
/* Deliver results as a stream of JSON events straight to
* json_event_callback, rather than as text through test->outfile.
* --json-stream sets both of these flags, so setting json_stream alone
* would leave iperf3 printing human-readable text as well. */
iperf_set_test_json_output(test, 1);
iperf_set_test_json_stream(test, 1);
iperf_set_test_json_callback(test, json_event_callback);
g_current_state = state;
/* Set temp directory for stream buffers (mkstemp in iperf_new_stream).
* On Android, /data/local/tmp is NOT writable from the app sandbox —
* use the app's cache dir instead. iperf_set_test_template() handles
* the strdup internally. */
iperf_set_test_template(test, g_tmp_template);
#undef FAIL_EARLY
/*
* Set up signal handling BEFORE calling iperf_run_client().
* iperf3's run() function uses setjmp/longjmp for signal handling.
* - setjmp returns 0 on first call (normal path)
* - setjmp returns non-zero when called via longjmp (signal path)
*
* `state` and `argv` are assigned before setjmp and never reassigned
* afterwards, so both paths observe the same values.
*/
if (setjmp(g_sigend_jmp_buf) == 0) {
/* Normal path — run the test */
signal(SIGPIPE, SIG_IGN); /* Ignore SIGPIPE for socket errors */
iperf_catch_sigend(sigend_handler);
g_stop = 0;
int ret = iperf_run_client(test);
LOGI("iperf_run_client returned: %d i_errno=%d strerror=%s",
ret, i_errno, iperf_strerror(i_errno));
/*
* We get here after iperf_run_client returns normally.
* If a signal arrives during iperf_run_client(), the
* sigend_handler fires longjmp() and we never reach here.
*/
iperf_cleanup_and_signal(env, state, argv, total_argc, ret, 0);
} else {
/*
* We got here via longjmp() from sigend_handler.
* iperf_cleanup_and_signal() frees everything, so there is nothing
* to unwind by hand here.
*/
LOGI("Test interrupted by signal %d", g_signed_sig);
g_stop = 1;
iperf_cleanup_and_signal(env, state, argv, total_argc, 0, 1);
}
}
/* ------------------------------------------------------------------ */
/* JNI: Gracefully stop a running iperf3 test. */
/* Signature: void forceStopIperfTest(IperfCallback cb) */
/* */
/* Runs on the platform thread while the test runs on a worker thread, */
/* so the whole body holds g_test_mutex: that keeps the worker from */
/* freeing g_test underneath us. */
/* ------------------------------------------------------------------ */
JNIEXPORT void JNICALL
Java_com_novoyuuparosk_iperf3gui_IperfRunner_forceStopIperfTest(
JNIEnv *env, jobject thiz, jobject callback) {
pthread_mutex_lock(&g_test_mutex);
int requested = 0;
if (g_test && !g_test->done) {
g_stop = 1;
g_test->done = 1;
iperf_set_send_state(g_test, IPERF_DONE);
/* Try to unblock select() on the control socket */
if (g_test->ctrl_sck >= 0) {
shutdown(g_test->ctrl_sck, SHUT_RDWR);
}
requested = 1;
}
pthread_mutex_unlock(&g_test_mutex);
if (!requested) return;
LOGI("Graceful stop requested");
}
/* ------------------------------------------------------------------ */
/* JNI: Get whether iperf is currently running. */
/* Signature: boolean isTestRunning() */
/* ------------------------------------------------------------------ */
JNIEXPORT jboolean JNICALL
Java_com_novoyuuparosk_iperf3gui_IperfRunner_isTestRunning(JNIEnv *env,
jobject thiz) {
pthread_mutex_lock(&g_test_mutex);
jboolean running = (g_test != NULL && !g_test->done);
pthread_mutex_unlock(&g_test_mutex);
return running;
}
/* ------------------------------------------------------------------ */
/* JNI: Set the temp directory for stream buffers. */
/* Signature: void setTempDir(String tmpDir) */
/* ------------------------------------------------------------------ */
JNIEXPORT void JNICALL
Java_com_novoyuuparosk_iperf3gui_IperfRunner_setTempDir(JNIEnv *env,
jobject thiz,
jstring tmpDir) {
if (!tmpDir) return;
const char *cstr = (*env)->GetStringUTFChars(env, tmpDir, NULL);
if (cstr) {
snprintf(g_tmp_template, sizeof(g_tmp_template), "%s/iperf3.XXXXXX", cstr);
(*env)->ReleaseStringUTFChars(env, tmpDir, cstr);
LOGI("Temp dir set to: %s", g_tmp_template);
}
}
@@ -0,0 +1,180 @@
package com.novoyuuparosk.iperf3gui
import android.os.Handler
import android.os.Looper
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.Executors
/**
* Kotlin binding layer for native iperf3 via JNI.
*
* iperf3 runs natively inside the app process — no Process.start() needed,
* no SELinux restrictions.
*
* Threading: runIperfLive() blocks for the entire duration of the test, so it
* runs on a dedicated worker thread. Running it on the platform thread would
* freeze the UI and — because output is delivered by posting to the main
* looper — stall every callback until the test was already over.
*/
object IperfRunner : FlutterPlugin {
private const val CHANNEL = "com.novoyuuparosk.iperf3gui/iperf"
private var channel: MethodChannel? = null
private var appContext: android.content.Context? = null
private val mainHandler = Handler(Looper.getMainLooper())
/** Serialises test runs: one test at a time, off the platform thread. */
private val runExecutor =
Executors.newSingleThreadExecutor { r -> Thread(r, "iperf3-run") }
private var nativeLibraryLoaded = false
init {
try {
android.util.Log.i("IperfRunner", "Loading native library...")
System.loadLibrary("iperf3jni")
nativeLibraryLoaded = true
android.util.Log.i("IperfRunner", "Native library loaded successfully")
} catch (e: UnsatisfiedLinkError) {
android.util.Log.e("IperfRunner", "Failed to load native library: ${e.message}")
e.printStackTrace()
}
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
appContext = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, CHANNEL)
channel?.setMethodCallHandler { call, result ->
when (call.method) {
"runIperf" -> handleRunIperf(call.argument<List<String>>("args"), result)
"stopIperf" -> handleStopIperf(result)
"isTestRunning" -> result.success(nativeLibraryLoaded && isTestRunning())
else -> result.notImplemented()
}
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Stop any in-flight test: its callbacks would otherwise fire into a
// dead channel, and the worker thread would keep the socket open.
if (nativeLibraryLoaded && isTestRunning()) {
forceStopIperfTest(NoopCallback)
}
channel?.setMethodCallHandler(null)
channel = null
appContext = null
}
private fun handleRunIperf(args: List<String>?, result: MethodChannel.Result) {
if (!nativeLibraryLoaded) {
result.error(
"NATIVE_LIB_MISSING",
"libiperf3jni.so failed to load — cannot run iperf3",
null,
)
return
}
if (args == null) {
result.error("INVALID_ARGS", "args must be a String array", null)
return
}
if (isTestRunning()) {
result.error("ALREADY_RUNNING", "A test is already running", null)
return
}
// Set temp dir to the app's cache dir — mkstemp() in iperf_new_stream()
// cannot write to /data/local/tmp from inside the app sandbox.
appContext?.cacheDir?.absolutePath?.let { setTempDir(it) }
val argv = args.toTypedArray()
val callback = mainThreadCallback()
// Hand back control immediately; progress arrives via the callback.
result.success(null)
runExecutor.execute {
try {
runIperfLive(argv, callback)
} catch (t: Throwable) {
android.util.Log.e("IperfRunner", "runIperfLive threw", t)
callback.onError("Native error: ${t.message}")
callback.onComplete()
}
}
}
private fun handleStopIperf(result: MethodChannel.Result) {
if (!nativeLibraryLoaded) {
result.success(null)
return
}
forceStopIperfTest(mainThreadCallback())
result.success(null)
}
/**
* Build a callback that forwards native output to Dart, hopping to the
* main thread first: MethodChannel.invokeMethod must not be called from
* the native reader thread.
*/
private fun mainThreadCallback(): IperfCallback = MainThreadCallback(
emitOutput = { line -> channel?.invokeMethod("onOutput", mapOf("output" to line)) },
emitError = { error -> channel?.invokeMethod("onError", mapOf("error" to error)) },
emitComplete = { channel?.invokeMethod("onComplete", emptyMap<String, Any>()) },
)
// ─────────────────────────────────────────────────────────────────
// Native method bindings
// ─────────────────────────────────────────────────────────────────
external fun runIperfLive(arguments: Array<String>, callback: IperfCallback)
external fun forceStopIperfTest(callback: IperfCallback)
external fun setTempDir(tmpDir: String)
external fun isTestRunning(): Boolean
/**
* Callback interface for iperf3 test output.
* Used by the native layer to stream results back to Flutter in real-time.
*/
interface IperfCallback {
fun onOutput(line: String)
fun onError(error: String)
fun onComplete()
}
/**
* Posts every callback to the main thread.
*
* The lambdas are named `emit*` rather than `on*` on purpose: a constructor
* property with the same name as the overriding method would make
* `onOutput(line)` resolve back to this class's own method — an endless
* post-and-re-post loop that never reaches the channel.
*/
private class MainThreadCallback(
private val emitOutput: (String) -> Unit,
private val emitError: (String) -> Unit,
private val emitComplete: () -> Unit,
) : IperfCallback {
override fun onOutput(line: String) {
mainHandler.post { emitOutput(line) }
}
override fun onError(error: String) {
mainHandler.post { emitError(error) }
}
override fun onComplete() {
mainHandler.post { emitComplete() }
}
}
/** Used when a stop is requested with no channel left to report to. */
private object NoopCallback : IperfCallback {
override fun onOutput(line: String) = Unit
override fun onError(error: String) = Unit
override fun onComplete() = Unit
}
}
@@ -1,5 +1,12 @@
package com.novoyuuparosk.iperf3gui
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity()
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Register IperfRunner as a FlutterPlugin for native iperf3 access
flutterEngine.plugins.add(IperfRunner)
}
}
Binary file not shown.
Binary file not shown.
+265
View File
@@ -0,0 +1,265 @@
# iperf3-gui
A Flutter-based Android GUI wrapper for iperf3, the popular network performance measurement tool.
## Architecture
```
lib/ # Flutter/Dart application layer
├── main.dart # Composition root: picks the Iperf3Runner impl
├── main_headless.dart # Runs a test with no widgets (see Layering)
├── domain/ # Pure Dart — no Flutter import
│ ├── models/
│ │ ├── test_configuration.dart # Server, port, duration, interval, parallel streams
│ │ ├── iperf3_result.dart # Raw output, exit code, stopped flag
│ │ ├── iperf3_event.dart # Parsed --json-stream reports
│ │ └── iperf3_session.dart # One run accumulated: samples, totals, stats
│ └── services/
│ └── iperf3_runner.dart # The contract the UI depends on
├── data/ # Transport — flutter/services only
│ └── services/
│ └── iperf3_service.dart # Iperf3Runner over MethodChannel → JNI
└── ui/ # Presentation
└── features/
└── test_run/
├── test_run_screen.dart # Inputs, GO/STOP, live transcript
├── test_run_view_model.dart # idle → running → completed/error
└── iperf3_event_formatter.dart # Events → iperf3-style text
android/app/src/main/
├── kotlin/ # Kotlin plugin layer
│ └── com/novoyuuparosk/iperf3gui/
│ ├── MainActivity.kt # Registers IperfRunner as FlutterPlugin
│ └── IperfRunner.kt # MethodChannel handler → native JNI calls
└── cpp/ # Native iperf3 library
├── CMakeLists.txt # Compiles iperf3 + cJSON + JNI into libiperf3jni.so
├── iperf_jni.c # JNI bridge: Java String[] → iperf3 API
├── iperf/ # iperf3 3.19 C source tree (see below)
├── iperf_config.h # Generated Android-compatible config header
└── iperf_config_android.h # Android-specific compile options
```
## Data Flow
```
User taps GO
→ Dart TestRunViewModel.runTest(config)
→ Iperf3Service.run(config) via MethodChannel
→ Kotlin IperfRunner: submits to the iperf3-run worker thread, returns at once
→ JNI C: build_argv() (prepends argv[0]) → iperf_parse_arguments()
→ iperf_run_client(test) [blocks on the worker thread]
→ iperf3 json_callback → one JSON event per report → CallVoidMethod
→ main-thread hop → Dart onOutput → Iperf3Event.parse()
→ Iperf3Service.events → view model: transcript + interval series + summary
→ On completion: Dart Completer resolves with Iperf3Result
```
## Layering
Dependencies point inward: `ui → domain ← data`, never the reverse.
| Layer | May import | Holds |
|---|---|---|
| `domain` | nothing else in this package, no Flutter | models and the `Iperf3Runner` contract |
| `data` | `domain`, `package:flutter/services.dart` | the MethodChannel/JNI implementation |
| `ui` | `domain` | widgets, view model, formatting |
| `main.dart` | everything | the single place that picks an implementation |
The UI depends on `Iperf3Runner`, not on `Iperf3Service`, so presentation names
no transport. That is what lets `test/widget_test.dart` drive the real widget
tree against an in-memory runner with no MethodChannel, native library or
device — and what keeps a test double at five stable members instead of
mirroring the transport's whole surface.
**The core does not need the UI.** What happened during a run is accumulated by
`Iperf3Session` in the domain layer — samples, totals, errors, and the mean,
peak and retransmit figures over them. The view model holds no run data of its
own and the screen derives no numbers; both read the session. Replacing the
presentation layer therefore means rewriting rendering, not re-deriving results.
`lib/main_headless.dart` is the standing proof: it runs a full test through
`Iperf3Service` and `Iperf3Session` with `WidgetsFlutterBinding` but no
`runApp` — no widget tree, no render surface. If the run path ever starts
needing the UI, it stops working.
```bash
flutter run -t lib/main_headless.dart \
--dart-define=IPERF_SERVER=192.168.2.182 --dart-define=IPERF_STREAMS=4
```
Use it when a figure has to be trustworthy. A 4-stream local run reports
**30.2% local CPU headless against 79.9% through the UI** — the phone is near
CPU-bound at gigabit rates, so a repainting transcript measurably depresses
throughput. Numbers taken through the UI are pessimistic.
`test/architecture_test.dart` enforces all of this by reading the actual
imports under `lib/`, including a check that no fourth layer directory has
appeared unnoticed. Change the layering there when the layering should change;
don't work around it.
## Key Design Decisions
### JNI Over Process Execution
Android SELinux Enforcing mode blocks `execve()` from the `untrusted_app` domain, making Process.start() of external binaries fail with Permission denied. Compiling iperf3 as a shared library linked into the app process eliminates this restriction entirely — iperf3 code runs in-process with no domain transitions needed.
### Threading
`runIperfLive()` blocks for the whole test, so Kotlin submits it to a dedicated
single-thread executor and hands control straight back to Dart. Running it on the
Flutter platform thread froze the UI and — because output is delivered by posting
to the main looper — stalled every callback until the test was already over.
### Callback Mechanism
The JNI bridge uses `CallVoidMethod` on stored `jmethodID`s to invoke the Kotlin `IperfCallback` interface, NOT function pointer casts. Direct function pointer casts caused SIGBUS crashes on ARM due to JNINativeInterface method ID layout differences.
### Output via json_callback, not a pipe
The test runs with `json_output` and `json_stream` set and a `json_callback`
installed, so iperf3 hands each report to the bridge as a JSON string and never
writes to `test->outfile`. `iperf.h` describes the hook as "allow user apps to
receive the JSON strings, instead of writing them to the output file" — this is
its intended use.
An earlier design piped `test->outfile` to a reader thread. That imposed two
constraints, both of which were gotten wrong and deadlocked every test: the
pipe's write end had to be closed *before* joining a reader blocked in
`fgets()`, and exactly one side could own the shared state struct. Reading from
the callback removes the pipe, the reader thread and both constraints. Don't
reintroduce it.
`json_callback` carries no user-data pointer, so the run state is reached
through a global published before `iperf_run_client()` and cleared afterwards;
only one test may run at a time, which `runIperfLive` enforces. The callback
fires on iperf3's reporting thread, so it resolves a `JNIEnv` for the calling
thread rather than reusing a stored one.
Dart parses each event into a sealed `Iperf3Event`; unrecognised output becomes
an `Iperf3LogLine` rather than being discarded. The UI rebuilds iperf3's text
layout from the events and keeps the interval series as data.
### Embedding a CLI in a long-lived process
iperf3's `main()` runs one test and exits, so anything it leaves behind is
reclaimed by the OS. Linked into an app process that runs tests repeatedly for
hours, every such leftover becomes this app's problem. What that costs in
practice:
- **Signal handlers are process-global.** `iperf_catch_sigend()` and
`signal(SIGPIPE, SIG_IGN)` change behaviour for the whole process, Flutter and
ART included. Handlers are restored to `SIG_DFL` when a test ends.
- **`g_sigend_jmp_buf` outlives its frame.** Harmless in a process about to
exit; a jump into a dead stack frame otherwise. Same fix as above.
- **Globals mean one test at a time.** `i_errno` and the bridge's own statics
hold a single test's worth of state, so concurrent runs are refused.
What iperf3 itself handles correctly, verified rather than assumed: per-stream
threads are joined (`iperf_client_api.c`), stream buffer fds are closed, and the
`mkstemp` temp file is `unlink`ed immediately after creation
(`iperf_api.c:4652`), so nothing accumulates in the cache dir. Measured across
four consecutive 4-stream runs on device, thread count returned to baseline
(33 → 40 mid-run → 34) and fd count returned exactly to baseline (175 → 183 →
175).
### Stop Handling
A stop shuts down the control socket, so `iperf_run_client()` returns -1 with
"Broken pipe". That is the expected outcome of a stop, not a failure, so the
bridge checks its own `g_stop` flag and reports a stop rather than an error; the
flag travels up to the UI as `Iperf3Result.stopped`.
### Async Pattern
Dart uses `Completer<Iperf3Result>` to await native completion. The MethodChannel callback handler resolves the completer on `onComplete`, with a timeout fallback that calls `stopIperf` on the native layer.
## iperf3 Source
- Version: 3.19
- Location: `android/app/src/main/cpp/iperf/`
- Files: Full C source tree including cJSON, headers, man pages, PEM keys
- Build: CMake compiles into `libiperf3jni.so` for `armeabi-v7a` and `arm64-v8a`
- Config: `iperf_config.h` generated from `iperf_config.h.in` with Android-compatible POSIX/threading macros
## Native JNI API
| Kotlin Method | JNI Signature | Purpose |
|---|---|---|
| `runIperfLive(String[], IperfCallback)` | `runIperfLive` | Run a test to completion — blocks, call off the platform thread |
| `forceStopIperfTest(IperfCallback)` | `forceStopIperfTest` | Graceful stop via shutdown() |
| `isTestRunning()` | `isTestRunning` | Check if test is in progress |
| `setTempDir(String)` | `setTempDir` | Point `mkstemp()` at the app cache dir |
## CI
Both jobs run on a **self-hosted runner registered at `android-stuff` org scope**,
targeted by the capability label `flutter-android` rather than by hostname.
`tool/setup_ci_runner.sh` provisions it.
It does not run on the Gitea host. Gitea lives on an aarch64 box, and Flutter
cannot build an Android APK on a Linux arm64 host at all — the SDK ships no
arm64-host `gen_snapshot` ([flutter#189724](https://github.com/flutter/flutter/issues/189724),
[#75864](https://github.com/flutter/flutter/issues/75864), open as of July 2026).
An x86_64 runner is a requirement, not a preference.
The runner is **host mode**: jobs use the machine's own Flutter, JDK and Android
SDK/NDK. CI therefore builds with the same toolchain that produced the verified
release, and no NDK is downloaded per run. The cost is that there is no
container boundary, which is what decides the next paragraph.
Jobs run as a dedicated unprivileged account, `gitea-runner`, in the
`flutterdev` group. In host mode the service account *is* the sandbox — there
is no other — and a workflow file is code that runs as whoever the service runs
as. A human account would hand every workflow that user's home directory and
SSH keys; root would be worse and would also miss the warm caches in that home,
re-downloading dependencies every run. The dedicated account keeps its own
Gradle and pub caches under `/var/lib/gitea-runner`, so the first Android build
is slow by design and later ones are not.
`flutterdev` is what grants write access to `/opt/flutter` and
`/opt/android-sdk`; both carry ACLs with `group:flutterdev:rwx` and matching
`default:` entries, so new files inherit it. The setup script proves that access
as the service account rather than assuming group membership is sufficient.
It installs a **system** service, not a user service: root-managed lifecycle,
starts at boot, no lingering session. That the service is root-managed is
unrelated to the jobs being unprivileged — `User=` in the unit settles the
second, and conflating the two is easy to do.
`sudo ./tool/setup_ci_runner.sh --check` validates this machine's toolchain
without creating anything.
Two jobs:
| Job | What it protects |
|---|---|
| `analyze` | `flutter analyze`, then `flutter test` — which includes `architecture_test.dart`, failing the build if a layer boundary is crossed |
| `release-apk` | builds the **minified** APK and runs `tool/check_jni_keep_rules.sh` |
The second exists because of a specific escape. A release APK that aborted on
every test shipped once, while every on-device check passed — because those
checks all used the debug build, which does not minify. Building the release APK
is necessary but not sufficient: a renamed JNI callback still produces a
*successful build* and a broken app. So the guard reads R8's `mapping.txt` for
the callback classes and methods, and the DEX string pool for the native entry
points. It is verified to fail: delete the keep rules from
`android/app/proguard-rules.pro`, rebuild, and it reports `MainThreadCallback`,
`onOutput`, `onError` and `onComplete` renamed.
## Building
Prerequisites:
- Flutter 3.47+
- Android SDK with platform android-36
- JDK 17
- NDK r28+
- CMake 3.22.1
```bash
flutter pub get
flutter build apk --debug
adb install build/app/outputs/flutter-apk/app-debug.apk
```
## Dependencies
- Dart: `provider`
- Android: CMake external native build
- Native: iperf3 3.19, cJSON (single file)
## License
MIT — bundled iperf3 3.19 is BSD-3-Clause licensed.
@@ -1,58 +0,0 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// Provides the path to the iperf3 binary on the device.
///
/// Call [initialize] in [main] (before [runApp]) to extract the
/// bundled asset and populate the cache. After initialization,
/// [path] returns a synchronous [String].
class Iperf3BinaryPathProvider {
static String? _cachedPath;
/// Extract the bundled iperf3 binary to the app's cache directory.
///
/// Call this once in [main] before [runApp].
static Future<void> initialize() async {
if (_cachedPath != null) return;
final cacheDir = await getApplicationCacheDirectory();
final binaryDir = Directory('${cacheDir.path}/iperf3');
if (!await binaryDir.exists()) {
await binaryDir.create(recursive: true);
}
final abi = await _detectAbi();
final assetPath = 'assets/iperf3/$abi/iperf3';
final bytes = await rootBundle.load(assetPath);
final file = File('${binaryDir.path}/iperf3');
await file.writeAsBytes(bytes.buffer.asUint8List());
// chmod +x the binary — /system/bin/sh is always present on Android
await Process.run(
'/system/bin/sh',
['-c', 'chmod +x "\$1"', '_', file.path],
);
_cachedPath = file.path;
}
/// The resolved path to the iperf3 binary.
///
/// Must call [initialize] first, or this will return a placeholder.
static String get path {
return _cachedPath ?? '/data/local/tmp/iperf3';
}
static Future<String> _detectAbi() async {
final result = await Process.run(
'getprop',
['ro.product.cpu.abi'],
);
final abi = result.stdout.toString().trim();
if (abi.contains('arm64')) return 'arm64-v8a';
return 'armeabi-v7a';
}
}
+193 -44
View File
@@ -1,63 +1,212 @@
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
/// Service that runs iperf3 via [Process.start] and captures output.
class Iperf3Service {
final String _binaryPath;
/// [Iperf3Runner] backed by the native iperf3 library over a MethodChannel.
///
/// On Android, iperf3 is compiled into the app as a shared library (.so).
/// This avoids SELinux restrictions on executing ELF binaries from assets.
///
/// The service talks to the native layer over a MethodChannel. The test runs
/// in `--json-stream` mode, so each native report arrives as one JSON object;
/// they are parsed and published on [events] while the test runs. [run]
/// resolves once the native layer reports completion, carrying the raw JSON
/// lines as the result's output.
class Iperf3Service implements Iperf3Runner {
Iperf3Service() {
_channel.setMethodCallHandler(_handleNativeCall);
}
// ignore: prefer_initializing_formals
Iperf3Service({required String binaryPath}) : _binaryPath = binaryPath;
/// A channel's handler is keyed by channel name in the binary messenger, so
/// it is process-global: constructing a second instance would take the
/// handler away from the first. The app composes exactly one, in main().
final MethodChannel _channel = const MethodChannel(_channelName);
static const String _channelName = 'com.novoyuuparosk.iperf3gui/iperf';
/// Exit code reported when the native layer signalled an error.
static const int exitCodeNativeError = 1;
/// Exit code reported when another test is already in flight.
static const int exitCodeBusy = -2;
/// Exit code reported when the test could not be started at all.
static const int exitCodeStartFailed = -1;
/// Exit code reported when the native layer never signalled completion.
static const int exitCodeTimeout = -3;
/// Events parsed from the native layer during the current run.
final StreamController<Iperf3Event> _eventController =
StreamController<Iperf3Event>.broadcast();
/// Live reports, one event per native report. Use this to render progress.
@override
Stream<Iperf3Event> get events => _eventController.stream;
/// Raw JSON lines received during the current (or most recent) run.
final List<String> _rawLines = [];
/// Completer that resolves when the native iperf3 test finishes.
Completer<Iperf3Result>? _runCompleter;
/// Whether iperf3 is currently running.
bool _isRunning = false;
@override
bool get isRunning => _isRunning;
/// Whether the native layer reported an error during this run.
bool _sawError = false;
/// Whether [stop] was called for the current run.
bool _stopRequested = false;
/// Cancel any running test.
@override
Future<void> stop() async {
if (!_isRunning) return;
_stopRequested = true;
try {
await _channel.invokeMethod('stopIperf');
} catch (e) {
_log('Iperf3Service: error stopping: $e');
}
}
/// Release the stream controller. Call from the owner's dispose().
@override
Future<void> dispose() async {
_channel.setMethodCallHandler(null);
await _eventController.close();
}
/// Run iperf3 with the given configuration.
///
/// Returns an [Iperf3Result] with stdout/stderr and exit code.
/// Returns an [Iperf3Result] with the full output and an exit code.
@override
Future<Iperf3Result> run(TestConfiguration config) async {
if (_isRunning) {
return const Iperf3Result(
rawOutput: 'Another test is already running.',
exitCode: exitCodeBusy,
);
}
_isRunning = true;
_sawError = false;
_stopRequested = false;
_rawLines.clear();
final args = config.toCliArgs();
_log('Iperf3Service: running: ${args.join(" ")}');
debugPrint(
'Iperf3Service: running $_binaryPath ${args.join(" ")}',
);
final completer = Completer<Iperf3Result>();
_runCompleter = completer;
final process = await Process.start(
_binaryPath,
args,
runInShell: false, // must execve directly — Android sh can't exec ELF binaries
);
try {
await _channel.invokeMethod('runIperf', {'args': args});
} catch (e, stackTrace) {
_log('Iperf3Service exception: $e');
_log('Iperf3Service stack: $stackTrace');
_finishRun();
return Iperf3Result(
rawOutput: 'Error starting iperf3: $e',
exitCode: exitCodeStartFailed,
);
}
final stdoutBuffer = <int>[];
final stderrBuffer = <int>[];
// Wait for the native layer to signal completion. The grace period covers
// DNS resolution and the 30s native connect timeout.
try {
return await completer.future.timeout(
config.duration + const Duration(seconds: 45),
onTimeout: () {
_log('Iperf3Service: timeout, stopping test');
// Ask the native layer to wind down, then release local state so a
// stuck run can never lock the UI out of starting another test.
_channel.invokeMethod('stopIperf').catchError((Object e) {
_log('Iperf3Service: error stopping after timeout: $e');
return null;
});
_publish(const Iperf3Error('Timed out waiting for iperf3 to finish.'));
return Iperf3Result(
rawOutput: _rawOutput(),
exitCode: exitCodeTimeout,
);
},
);
} catch (e) {
_log('Iperf3Service: error waiting for completion: $e');
return Iperf3Result(
rawOutput: 'Test failed: $e',
exitCode: exitCodeStartFailed,
);
} finally {
_finishRun();
}
}
final stdoutStream = process.stdout.transform(utf8.decoder).listen(
(data) {
stdoutBuffer.addAll(utf8.encode(data));
debugPrint('Iperf3Service stdout: $data');
},
);
final stderrStream = process.stderr.transform(utf8.decoder).listen(
(data) {
stderrBuffer.addAll(utf8.encode(data));
debugPrint('Iperf3Service stderr: $data');
},
);
/// Handle a callback pushed up from the Kotlin/JNI layer.
Future<void> _handleNativeCall(MethodCall call) async {
switch (call.method) {
case 'onOutput':
final output = call.arguments['output'] as String?;
if (output != null) {
_rawLines.add(output);
final event = Iperf3Event.parse(output);
// iperf3 reports a failed test as an `error` event rather than a
// non-zero return, so this is where most failures surface.
if (event is Iperf3Error) _sawError = true;
_publish(event);
}
case 'onError':
final error = call.arguments['error'] as String?;
if (error != null) {
// Not terminal on its own — iperf3 reports the failure reason and
// then completes, so wait for onComplete before resolving.
_sawError = true;
_publish(Iperf3Error(error));
}
case 'onComplete':
_log('Iperf3Service: test completed');
final completer = _runCompleter;
if (completer != null && !completer.isCompleted) {
completer.complete(
Iperf3Result(
rawOutput: _rawOutput(),
// A user-requested stop is a normal outcome, not a failure.
exitCode:
(_sawError && !_stopRequested) ? exitCodeNativeError : 0,
stopped: _stopRequested,
),
);
}
default:
_log('Iperf3Service: unhandled native call ${call.method}');
}
}
final exitCode = await process.exitCode;
void _publish(Iperf3Event event) {
if (!_eventController.isClosed) {
_eventController.add(event);
}
}
await stdoutStream.cancel();
await stderrStream.cancel();
/// The raw JSON stream as received, one event per line.
String _rawOutput() => _rawLines
.map((line) => line.endsWith('\n') ? line : '$line\n')
.join();
final rawOutput = utf8.decode(stdoutBuffer) + utf8.decode(stderrBuffer);
debugPrint(
'Iperf3Service: finished with exit code $exitCode, output length ${rawOutput.length}',
);
return Iperf3Result(
rawOutput: rawOutput,
exitCode: exitCode,
);
void _finishRun() {
_isRunning = false;
_runCompleter = null;
}
}
void _log(String message) =>
developer.log(message, name: 'Iperf3Service');
+238
View File
@@ -0,0 +1,238 @@
import 'dart:convert';
/// One report from iperf3's `--json-stream` output.
///
/// iperf3 emits `{"event": "<name>", "data": {...}}` per report; the events a
/// client run produces are `start`, `interval`, `end` and `error`. The bridge
/// adds `jni_stopped` when a run ended because the user stopped it.
sealed class Iperf3Event {
const Iperf3Event();
/// Parse one line of `--json-stream` output.
///
/// Returns an [Iperf3LogLine] for anything that is not a recognised JSON
/// event, so unexpected output is surfaced rather than dropped.
static Iperf3Event parse(String line) {
final trimmed = line.trim();
if (trimmed.isEmpty) return const Iperf3LogLine('');
if (!trimmed.startsWith('{')) return Iperf3LogLine(trimmed);
final Object? decoded;
try {
decoded = jsonDecode(trimmed);
} on FormatException {
return Iperf3LogLine(trimmed);
}
if (decoded is! Map<String, dynamic>) return Iperf3LogLine(trimmed);
final event = decoded['event'];
final data = decoded['data'];
if (event is! String) return Iperf3LogLine(trimmed);
final map = data is Map<String, dynamic> ? data : const <String, dynamic>{};
return switch (event) {
'start' => Iperf3Start.fromJson(map),
'interval' => Iperf3Interval.fromJson(map),
'end' => Iperf3End.fromJson(map),
'error' => Iperf3Error(_stringOf(data) ?? 'unknown error'),
'jni_stopped' => const Iperf3Stopped(),
_ => Iperf3LogLine(trimmed),
};
}
static String? _stringOf(Object? value) {
if (value is String) return value;
if (value is Map<String, dynamic>) {
final error = value['error'];
if (error is String) return error;
}
return null;
}
}
/// Connection details, emitted once at the start of a test.
class Iperf3Start extends Iperf3Event {
final String? targetHost;
final int? targetPort;
final int? streamCount;
final int? durationSeconds;
final String? protocol;
const Iperf3Start({
this.targetHost,
this.targetPort,
this.streamCount,
this.durationSeconds,
this.protocol,
});
factory Iperf3Start.fromJson(Map<String, dynamic> data) {
final connecting = data['connecting_to'];
final testStart = data['test_start'];
return Iperf3Start(
targetHost: connecting is Map ? connecting['host'] as String? : null,
targetPort: connecting is Map ? _asInt(connecting['port']) : null,
streamCount: testStart is Map ? _asInt(testStart['num_streams']) : null,
durationSeconds: testStart is Map ? _asInt(testStart['duration']) : null,
protocol: testStart is Map ? testStart['protocol'] as String? : null,
);
}
}
/// One reporting interval, summed across all streams.
class Iperf3Interval extends Iperf3Event {
final double start;
final double end;
final int bytes;
final double bitsPerSecond;
final int? retransmits;
final bool omitted;
/// Per-stream figures, when more than one stream is running.
final List<Iperf3StreamInterval> streams;
const Iperf3Interval({
required this.start,
required this.end,
required this.bytes,
required this.bitsPerSecond,
this.retransmits,
this.omitted = false,
this.streams = const [],
});
factory Iperf3Interval.fromJson(Map<String, dynamic> data) {
// Non-bidirectional runs use "sum"; bidir adds "sum_bidir_reverse".
final sum = data['sum'] ?? data['sum_bidir_reverse'];
final sumMap = sum is Map<String, dynamic> ? sum : const <String, dynamic>{};
final rawStreams = data['streams'];
return Iperf3Interval(
start: _asDouble(sumMap['start']) ?? 0,
end: _asDouble(sumMap['end']) ?? 0,
bytes: _asInt(sumMap['bytes']) ?? 0,
bitsPerSecond: _asDouble(sumMap['bits_per_second']) ?? 0,
retransmits: _asInt(sumMap['retransmits']),
omitted: sumMap['omitted'] == true,
streams: rawStreams is List
? rawStreams
.whereType<Map<String, dynamic>>()
.map(Iperf3StreamInterval.fromJson)
.toList(growable: false)
: const [],
);
}
}
/// One stream's contribution to an interval.
class Iperf3StreamInterval {
final int? socket;
final int bytes;
final double bitsPerSecond;
final int? retransmits;
final int? sendCwnd;
final int? rtt;
const Iperf3StreamInterval({
this.socket,
required this.bytes,
required this.bitsPerSecond,
this.retransmits,
this.sendCwnd,
this.rtt,
});
factory Iperf3StreamInterval.fromJson(Map<String, dynamic> data) {
return Iperf3StreamInterval(
socket: _asInt(data['socket']),
bytes: _asInt(data['bytes']) ?? 0,
bitsPerSecond: _asDouble(data['bits_per_second']) ?? 0,
retransmits: _asInt(data['retransmits']),
sendCwnd: _asInt(data['snd_cwnd']),
rtt: _asInt(data['rtt']),
);
}
}
/// Totals for the whole test, emitted once at the end.
class Iperf3End extends Iperf3Event {
final Iperf3Total? sent;
final Iperf3Total? received;
final double? hostCpuPercent;
final double? remoteCpuPercent;
const Iperf3End({
this.sent,
this.received,
this.hostCpuPercent,
this.remoteCpuPercent,
});
factory Iperf3End.fromJson(Map<String, dynamic> data) {
final cpu = data['cpu_utilization_percent'];
return Iperf3End(
sent: Iperf3Total.fromJson(
data['sum_sent'] ?? data['sum_sent_bidir_reverse']),
received: Iperf3Total.fromJson(
data['sum_received'] ?? data['sum_received_bidir_reverse']),
hostCpuPercent: cpu is Map ? _asDouble(cpu['host_total']) : null,
remoteCpuPercent: cpu is Map ? _asDouble(cpu['remote_total']) : null,
);
}
}
/// A sent or received total from the `end` event.
class Iperf3Total {
final double seconds;
final int bytes;
final double bitsPerSecond;
final int? retransmits;
const Iperf3Total({
required this.seconds,
required this.bytes,
required this.bitsPerSecond,
this.retransmits,
});
static Iperf3Total? fromJson(Object? data) {
if (data is! Map<String, dynamic>) return null;
return Iperf3Total(
seconds: _asDouble(data['seconds']) ?? 0,
bytes: _asInt(data['bytes']) ?? 0,
bitsPerSecond: _asDouble(data['bits_per_second']) ?? 0,
retransmits: _asInt(data['retransmits']),
);
}
}
/// iperf3 reported an error for this test.
class Iperf3Error extends Iperf3Event {
final String message;
const Iperf3Error(this.message);
}
/// The run ended because the user stopped it.
class Iperf3Stopped extends Iperf3Event {
const Iperf3Stopped();
}
/// Output that was not a recognised JSON event.
class Iperf3LogLine extends Iperf3Event {
final String text;
const Iperf3LogLine(this.text);
}
int? _asInt(Object? value) => switch (value) {
int v => v,
double v => v.round(),
String v => int.tryParse(v),
_ => null,
};
double? _asDouble(Object? value) => switch (value) {
double v => v,
int v => v.toDouble(),
String v => double.tryParse(v),
_ => null,
};
+5
View File
@@ -3,8 +3,13 @@ class Iperf3Result {
final String rawOutput;
final int exitCode;
/// Whether the run ended because the user asked it to stop, rather than
/// finishing its configured duration or failing.
final bool stopped;
const Iperf3Result({
required this.rawOutput,
required this.exitCode,
this.stopped = false,
});
}
+114
View File
@@ -0,0 +1,114 @@
import 'package:iperf3gui/domain/models/iperf3_event.dart';
/// The accumulated state of one iperf3 run.
///
/// Fed the event stream one report at a time, this is the answer to "what
/// happened during this test" — the interval series, the end totals, any
/// errors, and the statistics derived from them.
///
/// It lives in the domain layer on purpose: it is what has to be *correct*,
/// independent of how anything chooses to draw it. A different UI, a headless
/// run or a future export path all consume the same object, and none of them
/// re-derive a mean or re-decide whether a warm-up interval counts.
class Iperf3Session {
Iperf3Start? _start;
/// Connection details, once the run has reported them.
Iperf3Start? get start => _start;
final List<Iperf3Interval> _intervals = [];
/// Throughput samples in arrival order, warm-up intervals excluded.
List<Iperf3Interval> get intervals => List.unmodifiable(_intervals);
Iperf3End? _summary;
/// Sent/received totals, once the run has finished.
Iperf3End? get summary => _summary;
final List<String> _errors = [];
/// Errors reported during the run, in order.
List<String> get errors => List.unmodifiable(_errors);
bool _stopped = false;
/// Whether the run ended because it was asked to stop.
bool get stopped => _stopped;
/// Whether iperf3 reported any error.
bool get hasError => _errors.isNotEmpty;
/// Whether anything at all has been recorded.
bool get isEmpty =>
_start == null && _intervals.isEmpty && _summary == null && !hasError;
/// How many throughput samples have been recorded.
int get sampleCount => _intervals.length;
/// Throughput of the most recent sample, or null before the first one.
double? get latestBitsPerSecond =>
_intervals.isEmpty ? null : _intervals.last.bitsPerSecond;
/// Mean throughput across the samples, or null before the first one.
///
/// This is the mean of the per-interval rates, which is not identical to the
/// run's overall rate when intervals differ in length — prefer
/// [summary]'s totals for the headline figure once it is available.
double? get meanBitsPerSecond {
if (_intervals.isEmpty) return null;
final total = _intervals.fold<double>(0, (sum, i) => sum + i.bitsPerSecond);
return total / _intervals.length;
}
/// Highest sampled throughput, or null before the first sample.
double? get peakBitsPerSecond {
if (_intervals.isEmpty) return null;
return _intervals
.map((i) => i.bitsPerSecond)
.reduce((a, b) => a > b ? a : b);
}
/// Total bytes across the recorded samples.
int get sampledBytes => _intervals.fold<int>(0, (sum, i) => sum + i.bytes);
/// Total retransmits across the samples, or null if none were reported.
///
/// UDP tests report no retransmit counts, so null means "not measured"
/// rather than zero.
int? get totalRetransmits {
final reported =
_intervals.map((i) => i.retransmits).whereType<int>().toList();
if (reported.isEmpty) return null;
return reported.fold<int>(0, (sum, r) => sum + r);
}
/// Record one report.
void add(Iperf3Event event) {
switch (event) {
case Iperf3Start e:
_start = e;
case Iperf3Interval e:
// Warm-up intervals (-O) are marked omitted by iperf3 and must not
// drag the statistics down.
if (!e.omitted) _intervals.add(e);
case Iperf3End e:
_summary = e;
case Iperf3Error e:
_errors.add(e.message);
case Iperf3Stopped _:
_stopped = true;
case Iperf3LogLine _:
break;
}
}
/// Forget everything, ready for another run.
void clear() {
_start = null;
_intervals.clear();
_summary = null;
_errors.clear();
_stopped = false;
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ class TestConfiguration {
final int parallelStreams;
const TestConfiguration({
this.server = 'iperf.es.net',
this.server = 'speedtest.uztelecom.uz',
this.port = 5201,
this.duration = const Duration(seconds: 10),
this.interval = 1,
+36
View File
@@ -0,0 +1,36 @@
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
/// Runs iperf3 tests and reports what happens.
///
/// This is the whole contract the UI depends on. It is deliberately pure Dart —
/// no Flutter import — so the presentation layer never names a transport. The
/// Android build supplies a MethodChannel-and-JNI implementation
/// (`Iperf3Service`); a test, a desktop build or a headless tool can supply
/// its own without touching anything above this line.
abstract interface class Iperf3Runner {
/// Reports parsed from the running test, one event per iperf3 report.
///
/// A broadcast stream: it may have no listener, and late listeners miss
/// earlier events rather than replaying them.
Stream<Iperf3Event> get events;
/// Whether a test is in flight right now.
bool get isRunning;
/// Run a test to completion and resolve with its outcome.
///
/// Resolves rather than throws for an iperf3-level failure — the failure is
/// carried in [Iperf3Result.exitCode]. Implementations must always resolve,
/// including on internal error, so a caller can never be left awaiting.
Future<Iperf3Result> run(TestConfiguration config);
/// Ask a running test to stop early. A no-op when nothing is running.
///
/// The run's future still resolves, with [Iperf3Result.stopped] set.
Future<void> stop();
/// Release resources. The instance is unusable afterwards.
Future<void> dispose();
}
+12 -5
View File
@@ -1,15 +1,22 @@
import 'package:flutter/material.dart';
import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_screen.dart';
/// Application entry point, and the composition root.
///
/// This is the only place that names a concrete [Iperf3Runner]. Everything
/// above the data layer depends on the interface, so swapping the transport —
/// or running the UI against a fake — touches nothing but this file.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Iperf3BinaryPathProvider.initialize();
runApp(const Iperf3GuiApp());
runApp(Iperf3GuiApp(runner: Iperf3Service()));
}
class Iperf3GuiApp extends StatelessWidget {
const Iperf3GuiApp({super.key});
const Iperf3GuiApp({required this.runner, super.key});
final Iperf3Runner runner;
@override
Widget build(BuildContext context) {
@@ -19,7 +26,7 @@ class Iperf3GuiApp extends StatelessWidget {
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const TestRunScreen(),
home: TestRunScreen(runner: runner),
);
}
}
+105
View File
@@ -0,0 +1,105 @@
// Headless entry point: runs one iperf3 test with no widgets.
//
// This exists to keep the core honest. If anything in the run path ever starts
// depending on the UI, this stops compiling or stops working — a claim about
// separation that is checked rather than asserted.
//
// It is also the low-noise way to measure. The UI competes with the test for
// CPU (a gigabit run can put the phone near CPU-bound), so figures taken here
// are cleaner than figures taken with a transcript repainting alongside.
//
// flutter run -t lib/main_headless.dart \
// --dart-define=IPERF_SERVER=192.168.2.182 \
// --dart-define=IPERF_STREAMS=4
//
// Nothing below imports lib/ui, and test/architecture_test.dart enforces that.
import 'package:flutter/widgets.dart' show WidgetsFlutterBinding;
import 'package:flutter/foundation.dart' show debugPrint;
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_session.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
/// Test parameters, supplied at build time with --dart-define.
const String _server = String.fromEnvironment('IPERF_SERVER',
defaultValue: 'speedtest.uztelecom.uz');
const int _port = int.fromEnvironment('IPERF_PORT', defaultValue: 5201);
const int _seconds = int.fromEnvironment('IPERF_SECONDS', defaultValue: 10);
const int _streams = int.fromEnvironment('IPERF_STREAMS', defaultValue: 1);
Future<void> main() async {
// Required for the MethodChannel. Notably NOT runApp() — there is no widget
// tree, no render surface and no Flutter UI thread work in this process.
WidgetsFlutterBinding.ensureInitialized();
const config = TestConfiguration(
server: _server,
port: _port,
duration: Duration(seconds: _seconds),
parallelStreams: _streams,
);
final Iperf3Runner runner = Iperf3Service();
final session = Iperf3Session();
final subscription = runner.events.listen((event) {
session.add(event);
if (event is Iperf3Interval && !event.omitted) {
_out('interval ${event.start.toStringAsFixed(2)}-'
'${event.end.toStringAsFixed(2)}s '
'${_mbits(event.bitsPerSecond)} Mbit/s'
'${event.retransmits != null ? ' retr=${event.retransmits}' : ''}');
} else if (event is Iperf3Error) {
_out('error: ${event.message}');
}
});
_out('running iperf3 -c ${config.server} -p ${config.port} '
'-t ${config.duration.inSeconds} -P ${config.parallelStreams}');
final result = await runner.run(config);
await subscription.cancel();
await runner.dispose();
_report(result.exitCode, session);
}
void _report(int exitCode, Iperf3Session session) {
_out('--- result ---');
_out('exit code: $exitCode');
_out('samples: ${session.sampleCount}');
final mean = session.meanBitsPerSecond;
final peak = session.peakBitsPerSecond;
if (mean != null) _out('mean: ${_mbits(mean)} Mbit/s');
if (peak != null) _out('peak: ${_mbits(peak)} Mbit/s');
final retr = session.totalRetransmits;
_out('retransmits: ${retr ?? 'not measured'}');
final sent = session.summary?.sent;
final received = session.summary?.received;
if (sent != null) {
_out('sent: ${_mbits(sent.bitsPerSecond)} Mbit/s '
'over ${sent.seconds.toStringAsFixed(2)}s');
}
if (received != null) {
_out('received: ${_mbits(received.bitsPerSecond)} Mbit/s '
'over ${received.seconds.toStringAsFixed(2)}s');
}
final cpu = session.summary?.hostCpuPercent;
if (cpu != null) _out('local cpu: ${cpu.toStringAsFixed(1)}%');
if (session.stopped) _out('ended early: stopped');
for (final error in session.errors) {
_out('error: $error');
}
}
String _mbits(double bitsPerSecond) =>
(bitsPerSecond / 1e6).toStringAsFixed(1);
void _out(String line) => debugPrint('[iperf3-headless] $line');
@@ -0,0 +1,95 @@
import 'package:iperf3gui/domain/models/iperf3_event.dart';
/// Renders parsed iperf3 events as the kind of line iperf3 itself would print.
///
/// The app consumes `--json-stream`, so the familiar text layout has to be
/// rebuilt here. Keeping it in one pure function makes it testable and leaves
/// the structured events untouched for anything else that wants them.
String? formatIperf3Event(Iperf3Event event) {
return switch (event) {
Iperf3Start e => _formatStart(e),
Iperf3Interval e => _formatInterval(e),
Iperf3End e => _formatEnd(e),
Iperf3Error e => 'iperf3 error: ${e.message}',
Iperf3Stopped _ => 'Stopped by user.',
Iperf3LogLine e => e.text.isEmpty ? null : e.text,
};
}
String _formatStart(Iperf3Start e) {
final parts = <String>[];
if (e.targetHost != null) {
final port = e.targetPort != null ? ', port ${e.targetPort}' : '';
parts.add('Connecting to host ${e.targetHost}$port');
}
final detail = <String>[
if (e.protocol != null) e.protocol!,
if (e.streamCount != null)
'${e.streamCount} stream${e.streamCount == 1 ? '' : 's'}',
if (e.durationSeconds != null) '${e.durationSeconds}s',
];
if (detail.isNotEmpty) parts.add(detail.join(' · '));
return parts.isEmpty ? '' : parts.join('\n');
}
String _formatInterval(Iperf3Interval e) {
final window = '${_seconds(e.start)}-${_seconds(e.end)} sec';
final line = StringBuffer('[SUM] $window '
'${formatBytes(e.bytes)} ${formatBitrate(e.bitsPerSecond)}');
if (e.retransmits != null) {
line.write(' ${e.retransmits} retr');
}
if (e.omitted) line.write(' (omitted)');
return line.toString();
}
String _formatEnd(Iperf3End e) {
final lines = <String>['- ' * 20];
final sent = e.sent;
final received = e.received;
if (sent != null) {
lines.add('[SUM] 0.00-${_seconds(sent.seconds)} sec '
'${formatBytes(sent.bytes)} ${formatBitrate(sent.bitsPerSecond)}'
'${sent.retransmits != null ? ' ${sent.retransmits} retr' : ''}'
' sender');
}
if (received != null) {
lines.add('[SUM] 0.00-${_seconds(received.seconds)} sec '
'${formatBytes(received.bytes)} '
'${formatBitrate(received.bitsPerSecond)} receiver');
}
if (e.hostCpuPercent != null) {
lines.add('CPU: local ${e.hostCpuPercent!.toStringAsFixed(1)}%'
'${e.remoteCpuPercent != null ? ', remote ${e.remoteCpuPercent!.toStringAsFixed(1)}%' : ''}');
}
return lines.join('\n');
}
String _seconds(double value) => value.toStringAsFixed(2);
/// Byte totals the way iperf3 prints them — `93.8 MBytes`.
String formatBytes(int bytes) {
const units = ['Bytes', 'KBytes', 'MBytes', 'GBytes', 'TBytes'];
var value = bytes.toDouble();
var unit = 0;
while (value >= 1024 && unit < units.length - 1) {
value /= 1024;
unit++;
}
final precision = value >= 100 ? 0 : (value >= 10 ? 1 : 2);
return '${value.toStringAsFixed(precision)} ${units[unit]}';
}
/// Bitrates the way iperf3 prints them — `786 Mbits/sec`. Decimal units, since
/// network rates are powers of ten while transfer totals are powers of two.
String formatBitrate(double bitsPerSecond) {
const units = ['bits/sec', 'Kbits/sec', 'Mbits/sec', 'Gbits/sec'];
var value = bitsPerSecond;
var unit = 0;
while (value >= 1000 && unit < units.length - 1) {
value /= 1000;
unit++;
}
final precision = value >= 100 ? 0 : (value >= 10 ? 1 : 2);
return '${value.toStringAsFixed(precision)} ${units[unit]}';
}
+219 -68
View File
@@ -1,21 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
import 'package:iperf3gui/ui/features/test_run/iperf3_event_formatter.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart';
/// Main test run screen.
///
/// Takes an [Iperf3Runner] rather than constructing one, so this layer names no
/// transport: the Android app injects the JNI-backed implementation from
/// main(), and a test injects a fake.
class TestRunScreen extends StatelessWidget {
const TestRunScreen({super.key});
const TestRunScreen({required this.runner, super.key});
final Iperf3Runner runner;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TestRunViewModel(
iperf3Service: Iperf3Service(
binaryPath: Iperf3BinaryPathProvider.path,
),
),
create: (_) => TestRunViewModel(iperf3Service: runner),
child: const _TestRunContent(),
);
}
@@ -29,109 +33,227 @@ class _TestRunContent extends StatefulWidget {
}
class _TestRunContentState extends State<_TestRunContent> {
final _serverController = TextEditingController(text: 'iperf.es.net');
final _portController = TextEditingController(text: '5201');
static const _defaults = TestConfiguration();
final _serverController =
TextEditingController(text: _defaults.server);
final _portController =
TextEditingController(text: _defaults.port.toString());
final _durationController =
TextEditingController(text: _defaults.duration.inSeconds.toString());
final _streamsController =
TextEditingController(text: _defaults.parallelStreams.toString());
final _outputScrollController = ScrollController();
@override
void dispose() {
_serverController.dispose();
_portController.dispose();
_durationController.dispose();
_streamsController.dispose();
_outputScrollController.dispose();
super.dispose();
}
TestConfiguration _readConfiguration() {
return TestConfiguration(
server: _serverController.text.trim(),
port: int.tryParse(_portController.text) ?? _defaults.port,
duration: Duration(
seconds: int.tryParse(_durationController.text) ??
_defaults.duration.inSeconds,
),
parallelStreams:
int.tryParse(_streamsController.text) ?? _defaults.parallelStreams,
);
}
/// Keep the newest output in view, but only once the frame that added it has
/// been laid out.
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_outputScrollController.hasClients) return;
_outputScrollController
.jumpTo(_outputScrollController.position.maxScrollExtent);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('iperf3-gui'),
actions: [
Consumer<TestRunViewModel>(
builder: (context, vm, _) => IconButton(
tooltip: 'Copy output',
icon: const Icon(Icons.copy_all),
onPressed: vm.output.isEmpty
? null
: () {
Clipboard.setData(ClipboardData(text: vm.output));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Output copied')),
);
},
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Server input
TextField(
controller: _serverController,
autocorrect: false,
decoration: const InputDecoration(
labelText: 'Server',
hintText: 'e.g. iperf.es.net or 192.168.1.1',
hintText: 'e.g. speedtest.uztelecom.uz or 192.168.1.1',
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextField(
controller: _portController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Port'),
),
),
const SizedBox(width: 12),
Expanded(
child: TextField(
controller: _durationController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Seconds'),
),
),
const SizedBox(width: 12),
Expanded(
child: TextField(
controller: _streamsController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Streams'),
),
),
],
),
const SizedBox(height: 16),
// Port input
TextField(
controller: _portController,
decoration: const InputDecoration(
labelText: 'Port',
hintText: '5201',
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
// Go button
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
if (vm.isRunning) {
return OutlinedButton.icon(
onPressed: vm.stopTest,
icon: const Icon(Icons.stop),
label: const Text('STOP'),
);
}
return ElevatedButton(
onPressed: vm.state == TestRunState.running
? null
: () {
final config = TestConfiguration(
server: _serverController.text,
port: int.tryParse(_portController.text) ?? 5201,
);
vm.runTest(config);
},
child: vm.state == TestRunState.running
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('GO'),
onPressed: () => vm.runTest(_readConfiguration()),
child: const Text('GO'),
);
},
),
const SizedBox(height: 16),
const SizedBox(height: 12),
// Status indicator
// Status line
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final statusText = switch (vm.state) {
TestRunState.idle => '',
TestRunState.running => 'Running...',
TestRunState.completed => 'Test complete',
TestRunState.error => 'Error',
final scheme = Theme.of(context).colorScheme;
final (String label, Color color) = switch (vm.state) {
TestRunState.idle => ('Ready', scheme.onSurfaceVariant),
TestRunState.running => ('Running…', scheme.primary),
TestRunState.completed => (
vm.wasStopped ? 'Stopped' : 'Test complete',
vm.wasStopped ? scheme.onSurfaceVariant : scheme.primary,
),
TestRunState.error => (
'Error${vm.result != null ? ' (exit ${vm.result!.exitCode})' : ''}',
scheme.error,
),
};
return Text(
statusText,
style: TextStyle(
color: vm.state == TestRunState.error
? Colors.red
: Colors.grey,
return Row(
children: [
if (vm.isRunning)
const Padding(
padding: EdgeInsets.only(right: 8),
child: SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
Text(label, style: TextStyle(color: color)),
],
);
},
),
const SizedBox(height: 12),
// Live throughput figures. Every number here is read off the
// session; none are computed in the widget tree.
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final session = vm.session;
final latest = session.latestBitsPerSecond;
final mean = session.meanBitsPerSecond;
final peak = session.peakBitsPerSecond;
if (latest == null || mean == null || peak == null) {
return const SizedBox.shrink();
}
final total = session.summary?.sent;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_Stat(
label: total != null ? 'total' : 'now',
value: formatBitrate(total?.bitsPerSecond ?? latest),
),
_Stat(label: 'avg', value: formatBitrate(mean)),
_Stat(label: 'peak', value: formatBitrate(peak)),
_Stat(
label: 'samples', value: '${session.sampleCount}'),
],
),
);
},
),
const SizedBox(height: 16),
// Raw output text box
// Live output
Expanded(
child: Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final output = vm.result?.rawOutput ?? '';
return TextField(
controller: TextEditingController(text: output),
decoration: const InputDecoration(
labelText: 'Raw Output',
border: OutlineInputBorder(),
),
maxLines: null,
readOnly: true,
);
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: BorderRadius.circular(4),
),
child: Consumer<TestRunViewModel>(
builder: (context, vm, _) {
_scrollToBottom();
return SingleChildScrollView(
controller: _outputScrollController,
child: SelectableText(
vm.output.isEmpty
? 'Output will appear here.'
: vm.output,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
);
},
),
),
),
],
@@ -140,3 +262,32 @@ class _TestRunContentState extends State<_TestRunContent> {
);
}
}
/// One figure in the throughput row.
class _Stat extends StatelessWidget {
const _Stat({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: theme.textTheme.labelSmall
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
),
Text(
value,
style: theme.textTheme.titleSmall
?.copyWith(fontFeatures: const [FontFeature.tabularFigures()]),
),
],
);
}
}
@@ -1,14 +1,37 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/iperf3_session.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
import 'package:iperf3gui/ui/features/test_run/iperf3_event_formatter.dart';
/// State of the test run.
enum TestRunState { idle, running, completed, error }
/// ViewModel for managing test run state.
///
/// Presentation only. What happened during a run is accumulated by
/// [Iperf3Session] in the domain layer; this class subscribes to
/// [Iperf3Runner.events], feeds that session, keeps a formatted transcript for
/// display, and decides when to repaint.
class TestRunViewModel extends ChangeNotifier {
final Iperf3Service iperf3Service;
final Iperf3Runner iperf3Service;
/// How often to notify listeners while output is arriving. iperf3 can report
/// several times a second across many streams, and each rebuild re-renders
/// the whole transcript — so repaints are coalesced instead of one per event.
static const Duration displayInterval = Duration(milliseconds: 250);
TestRunViewModel({required this.iperf3Service}) {
_eventSubscription = iperf3Service.events.listen(_onEvent);
}
StreamSubscription<Iperf3Event>? _eventSubscription;
Timer? _repaintTimer;
bool _dirty = false;
TestRunState _state = TestRunState.idle;
TestRunState get state => _state;
@@ -19,39 +42,110 @@ class TestRunViewModel extends ChangeNotifier {
TestConfiguration? _currentConfig;
TestConfiguration? get currentConfig => _currentConfig;
TestRunViewModel({required this.iperf3Service});
final StringBuffer _transcript = StringBuffer();
/// Human-readable output accumulated so far. Display only — the structured
/// record of the run is [session].
String get output => _transcript.toString();
final Iperf3Session _session = Iperf3Session();
/// The accumulated run: interval series, totals, errors and their statistics.
Iperf3Session get session => _session;
/// Whether a test is currently in flight.
bool get isRunning => _state == TestRunState.running;
/// Whether the most recent finished run ended because the user stopped it.
bool get wasStopped => _result?.stopped ?? false;
/// Start a test with the given configuration.
Future<void> runTest(TestConfiguration config) async {
if (_state == TestRunState.running) return;
if (!config.isValid) {
_resetRunData();
_transcript
.writeln('Invalid configuration: check the server address and port.');
_state = TestRunState.error;
notifyListeners();
return;
}
_resetRunData();
_state = TestRunState.running;
_currentConfig = config;
_result = null;
notifyListeners();
try {
final result = await iperf3Service.run(config);
_result = result;
_state = result.exitCode == 0
? TestRunState.completed
: TestRunState.error;
if (_transcript.isEmpty && result.rawOutput.isNotEmpty) {
_transcript.write(result.rawOutput);
}
_state =
result.exitCode == 0 ? TestRunState.completed : TestRunState.error;
} catch (e) {
_transcript.writeln('Test failed: $e');
_state = TestRunState.error;
} finally {
_stopRepaintTimer();
notifyListeners();
}
}
/// Request that a running test stop early.
Future<void> stopTest() async {
if (_state != TestRunState.running) return;
await iperf3Service.stop();
}
/// Reset to idle state.
void reset() {
_resetRunData();
_state = TestRunState.idle;
_result = null;
_currentConfig = null;
notifyListeners();
}
void _resetRunData() {
_result = null;
_transcript.clear();
_session.clear();
_dirty = false;
}
void _onEvent(Iperf3Event event) {
_session.add(event);
final line = formatIperf3Event(event);
if (line != null && line.isNotEmpty) {
_transcript.writeln(line);
}
_scheduleRepaint();
}
/// Coalesce repaints: mark dirty and let a timer flush at [displayInterval].
void _scheduleRepaint() {
_dirty = true;
_repaintTimer ??= Timer.periodic(displayInterval, (_) {
if (!_dirty) return;
_dirty = false;
notifyListeners();
});
}
void _stopRepaintTimer() {
_repaintTimer?.cancel();
_repaintTimer = null;
_dirty = false;
}
@override
void dispose() {
_stopRepaintTimer();
_eventSubscription?.cancel();
iperf3Service.dispose();
super.dispose();
}
}
-1
View File
@@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
+1 -193
View File
@@ -1,14 +1,6 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
@@ -41,14 +33,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
url: "https://pub.dev"
source: hosted
version: "1.2.1"
collection:
dependency: transitive
description:
@@ -57,14 +41,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
fake_async:
dependency: transitive
description:
@@ -73,14 +49,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -99,38 +67,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
hooks:
dependency: transitive
description:
name: hooks
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
jni:
dependency: transitive
description:
name: jni
sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3
url: "https://pub.dev"
source: hosted
version: "1.0.3"
jni_flutter:
dependency: transitive
description:
name: jni_flutter
sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
jni_util:
dependency: transitive
description:
name: jni_util
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
leak_tracker:
dependency: transitive
description:
@@ -163,14 +99,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -203,22 +131,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e
url: "https://pub.dev"
source: hosted
version: "9.5.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
url: "https://pub.dev"
source: hosted
version: "3.0.0"
path:
dependency: transitive
description:
@@ -227,70 +139,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
url: "https://pub.dev"
source: hosted
version: "2.1.6"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: "direct main"
description:
@@ -299,22 +147,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
record_use:
dependency: transitive
description:
name: record_use
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
sky_engine:
dependency: transitive
description: flutter
@@ -368,14 +200,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.12"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
@@ -392,22 +216,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "15.3.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.13.1 <4.0.0"
flutter: ">=3.38.4"
flutter: ">=3.18.0-18.0.pre.54"
+3 -63
View File
@@ -1,37 +1,16 @@
name: iperf3gui
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
description: "A Flutter-based Android GUI wrapper for iperf3."
publish_to: 'none'
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 0.1.0+1
environment:
sdk: ^3.13.1
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
path_provider: ^2.1.4
provider: ^6.1.2
dev_dependencies:
@@ -39,44 +18,5 @@ dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/iperf3/arm64-v8a/iperf3
- assets/iperf3/armeabi-v7a/iperf3
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
+144
View File
@@ -0,0 +1,144 @@
// Enforces the layering. A convention nobody checks is a convention that
// decays, so these read the real import statements in lib/ and fail on a
// violation rather than trusting review to catch it.
//
// The rule, in one line: dependencies point inward. ui → domain ← data, and
// never the reverse.
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('layering', () {
test('domain imports no Flutter at all', () {
final offenders = _importsMatching(
'lib/domain',
(import) => import.startsWith('package:flutter/'),
);
expect(
offenders,
isEmpty,
reason: 'The domain layer must stay pure Dart so it can be reused '
'without a Flutter runtime. Offending imports:\n'
'${_render(offenders)}',
);
});
test('domain does not depend on data or ui', () {
final offenders = _importsMatching(
'lib/domain',
(import) =>
import.contains('/data/') ||
import.contains('/ui/') ||
import.contains('main.dart'),
);
expect(offenders, isEmpty,
reason: 'The domain layer is the innermost layer; it may depend on '
'nothing else in this package.\n${_render(offenders)}');
});
test('data does not depend on ui', () {
final offenders = _importsMatching(
'lib/data',
(import) => import.contains('/ui/') || import.contains('main.dart'),
);
expect(offenders, isEmpty,
reason: 'The data layer must not know about presentation.\n'
'${_render(offenders)}');
});
test('data imports no Flutter widget libraries', () {
// flutter/services is allowed: MethodChannel lives there, and that is
// the whole reason this layer is Flutter-aware at all.
const allowed = {'package:flutter/services.dart'};
final offenders = _importsMatching(
'lib/data',
(import) =>
import.startsWith('package:flutter/') && !allowed.contains(import),
);
expect(
offenders,
isEmpty,
reason: 'The data layer may import package:flutter/services.dart only. '
'Anything else drags presentation into the transport.\n'
'${_render(offenders)}',
);
});
test('ui does not depend on a concrete data implementation', () {
// The UI talks to Iperf3Runner. main.dart is the composition root and is
// the one file allowed to name an implementation.
final offenders = _importsMatching(
'lib/ui',
(import) => import.contains('/data/'),
);
expect(
offenders,
isEmpty,
reason: 'Presentation must depend on domain/services/iperf3_runner.dart '
'rather than on a transport. Only main.dart may pick an '
'implementation.\n${_render(offenders)}',
);
});
test('the headless entry point does not depend on ui', () {
// main_headless.dart is the standing proof that the core runs without a
// widget tree; an import from lib/ui would quietly end that.
final offenders = _importsMatching(
'lib',
(import) => import.contains('/ui/'),
onlyFiles: {'lib/main_headless.dart'},
);
expect(
offenders,
isEmpty,
reason: 'The headless entry point must run the core with no UI.\n'
'${_render(offenders)}',
);
});
test('every layer directory is actually covered by these rules', () {
// Guards against a new top-level layer appearing and silently escaping
// every rule above.
final layers = Directory('lib')
.listSync()
.whereType<Directory>()
.map((d) => d.path.split(Platform.pathSeparator).last)
.toSet();
expect(
layers,
equals({'domain', 'data', 'ui'}),
reason: 'A new layer appeared under lib/. Add rules for it here.',
);
});
});
}
/// Every `(file, import)` pair under [directory] whose import satisfies [test].
List<({String file, String import})> _importsMatching(
String directory,
bool Function(String import) test, {
Set<String>? onlyFiles,
}) {
final results = <({String file, String import})>[];
final dir = Directory(directory);
if (!dir.existsSync()) return results;
final pattern = RegExp(r'''^\s*import\s+['"]([^'"]+)['"]''', multiLine: true);
for (final entity in dir.listSync(recursive: true)) {
if (entity is! File || !entity.path.endsWith('.dart')) continue;
if (onlyFiles != null && !onlyFiles.contains(entity.path)) continue;
final source = entity.readAsStringSync();
for (final match in pattern.allMatches(source)) {
final import = match.group(1)!;
if (test(import)) {
results.add((file: entity.path, import: import));
}
}
}
return results;
}
String _render(List<({String file, String import})> offenders) =>
offenders.map((o) => ' ${o.file} → ${o.import}').join('\n');
+107
View File
@@ -0,0 +1,107 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
void main() {
group('Iperf3Event.parse', () {
test('parses a start event', () {
const line = '{"event":"start","data":{'
'"connecting_to":{"host":"192.168.2.182","port":5201},'
'"test_start":{"protocol":"TCP","num_streams":4,"duration":10}}}';
final event = Iperf3Event.parse(line);
expect(event, isA<Iperf3Start>());
final start = event as Iperf3Start;
expect(start.targetHost, equals('192.168.2.182'));
expect(start.targetPort, equals(5201));
expect(start.streamCount, equals(4));
expect(start.durationSeconds, equals(10));
expect(start.protocol, equals('TCP'));
});
test('parses an interval event with per-stream detail', () {
const line = '{"event":"interval","data":{'
'"streams":[{"socket":143,"bytes":49283072,'
'"bits_per_second":394264576.0,"retransmits":0,"snd_cwnd":2023424,'
'"rtt":3521}],'
'"sum":{"start":0.0,"end":1.000151,"seconds":1.000151,'
'"bytes":98566144,"bits_per_second":788410352.0,"retransmits":0,'
'"omitted":false,"sender":true}}}';
final event = Iperf3Event.parse(line);
expect(event, isA<Iperf3Interval>());
final interval = event as Iperf3Interval;
expect(interval.bytes, equals(98566144));
expect(interval.bitsPerSecond, closeTo(788410352.0, 0.1));
expect(interval.retransmits, equals(0));
expect(interval.omitted, isFalse);
expect(interval.streams, hasLength(1));
expect(interval.streams.single.socket, equals(143));
expect(interval.streams.single.rtt, equals(3521));
});
test('marks omitted intervals', () {
const line = '{"event":"interval","data":{"streams":[],'
'"sum":{"start":0.0,"end":1.0,"bytes":1,"bits_per_second":8.0,'
'"omitted":true}}}';
final interval = Iperf3Event.parse(line) as Iperf3Interval;
expect(interval.omitted, isTrue);
});
test('parses an end event with sent and received totals', () {
const line = '{"event":"end","data":{'
'"sum_sent":{"seconds":10.0,"bytes":987654321,'
'"bits_per_second":790123456.0,"retransmits":7},'
'"sum_received":{"seconds":10.01,"bytes":987000000,'
'"bits_per_second":789000000.0},'
'"cpu_utilization_percent":{"host_total":12.5,"remote_total":3.25}}}';
final end = Iperf3Event.parse(line) as Iperf3End;
expect(end.sent!.bytes, equals(987654321));
expect(end.sent!.retransmits, equals(7));
expect(end.received!.bitsPerSecond, closeTo(789000000.0, 0.1));
expect(end.hostCpuPercent, closeTo(12.5, 0.01));
expect(end.remoteCpuPercent, closeTo(3.25, 0.01));
});
test('falls back to bidir-reverse keys when present', () {
const line = '{"event":"interval","data":{"streams":[],'
'"sum_bidir_reverse":{"start":0.0,"end":1.0,"bytes":42,'
'"bits_per_second":336.0}}}';
final interval = Iperf3Event.parse(line) as Iperf3Interval;
expect(interval.bytes, equals(42));
});
test('parses an error event', () {
const line = '{"event":"error","data":"unable to connect to server"}';
final event = Iperf3Event.parse(line);
expect(event, isA<Iperf3Error>());
expect((event as Iperf3Error).message,
equals('unable to connect to server'));
});
test('parses the bridge stop marker', () {
final event = Iperf3Event.parse('{"event":"jni_stopped","data":{}}');
expect(event, isA<Iperf3Stopped>());
});
test('surfaces non-JSON output as a log line rather than dropping it', () {
final event = Iperf3Event.parse('iperf3: something unexpected');
expect(event, isA<Iperf3LogLine>());
expect((event as Iperf3LogLine).text,
equals('iperf3: something unexpected'));
});
test('surfaces malformed JSON as a log line', () {
final event = Iperf3Event.parse('{"event":"interval",');
expect(event, isA<Iperf3LogLine>());
});
test('treats an unknown event name as a log line', () {
final event = Iperf3Event.parse('{"event":"future_thing","data":{}}');
expect(event, isA<Iperf3LogLine>());
});
test('tolerates a missing data object', () {
final event = Iperf3Event.parse('{"event":"interval"}');
expect(event, isA<Iperf3Interval>());
expect((event as Iperf3Interval).bytes, equals(0));
});
});
}
@@ -11,5 +11,15 @@ void main() {
expect(result.rawOutput, equals('Test complete'));
expect(result.exitCode, equals(0));
});
test('stopped defaults to false', () {
const result = Iperf3Result(rawOutput: 'x', exitCode: 0);
expect(result.stopped, isFalse);
});
test('stopped is preserved when set', () {
const result = Iperf3Result(rawOutput: 'x', exitCode: 0, stopped: true);
expect(result.stopped, isTrue);
});
});
}
+135
View File
@@ -0,0 +1,135 @@
// Pure-Dart tests for the run aggregate. No widgets, no bindings, no channel —
// this is the part that has to be correct regardless of what draws it.
import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_session.dart';
void main() {
Iperf3Interval sample(double start, double bits,
{int bytes = 1000, int? retransmits, bool omitted = false}) =>
Iperf3Interval(
start: start,
end: start + 1,
bytes: bytes,
bitsPerSecond: bits,
retransmits: retransmits,
omitted: omitted,
);
group('Iperf3Session', () {
test('starts empty', () {
final session = Iperf3Session();
expect(session.isEmpty, isTrue);
expect(session.sampleCount, isZero);
expect(session.intervals, isEmpty);
expect(session.start, isNull);
expect(session.summary, isNull);
expect(session.errors, isEmpty);
expect(session.stopped, isFalse);
expect(session.hasError, isFalse);
});
test('statistics are null before any sample', () {
final session = Iperf3Session();
expect(session.latestBitsPerSecond, isNull);
expect(session.meanBitsPerSecond, isNull);
expect(session.peakBitsPerSecond, isNull);
expect(session.totalRetransmits, isNull);
});
test('records start, intervals and summary', () {
final session = Iperf3Session()
..add(const Iperf3Start(targetHost: 'host', targetPort: 5201))
..add(sample(0, 100))
..add(const Iperf3End(
sent: Iperf3Total(seconds: 1, bytes: 10, bitsPerSecond: 80),
));
expect(session.isEmpty, isFalse);
expect(session.start!.targetHost, equals('host'));
expect(session.sampleCount, equals(1));
expect(session.summary!.sent!.bitsPerSecond, equals(80));
});
test('excludes omitted warm-up intervals from the series', () {
final session = Iperf3Session()
..add(sample(0, 10, omitted: true))
..add(sample(1, 100))
..add(sample(2, 200));
expect(session.sampleCount, equals(2));
expect(session.meanBitsPerSecond, equals(150),
reason: 'the omitted 10 would have pulled the mean to ~103');
});
test('computes mean, peak and latest over the samples', () {
final session = Iperf3Session()
..add(sample(0, 100))
..add(sample(1, 400))
..add(sample(2, 250));
expect(session.latestBitsPerSecond, equals(250));
expect(session.peakBitsPerSecond, equals(400));
expect(session.meanBitsPerSecond, equals(250));
});
test('sums sampled bytes', () {
final session = Iperf3Session()
..add(sample(0, 1, bytes: 1500))
..add(sample(1, 1, bytes: 2500));
expect(session.sampledBytes, equals(4000));
});
test('sums retransmits when reported', () {
final session = Iperf3Session()
..add(sample(0, 1, retransmits: 2))
..add(sample(1, 1, retransmits: 5));
expect(session.totalRetransmits, equals(7));
});
test('retransmits stay null when never reported, not zero', () {
// UDP reports no retransmit counts; "not measured" must not read as 0.
final session = Iperf3Session()
..add(sample(0, 1))
..add(sample(1, 1));
expect(session.totalRetransmits, isNull);
});
test('collects errors in order and flags them', () {
final session = Iperf3Session()
..add(const Iperf3Error('first'))
..add(const Iperf3Error('second'));
expect(session.hasError, isTrue);
expect(session.errors, equals(['first', 'second']));
expect(session.isEmpty, isFalse);
});
test('records a stop', () {
final session = Iperf3Session()..add(const Iperf3Stopped());
expect(session.stopped, isTrue);
});
test('ignores log lines', () {
final session = Iperf3Session()..add(const Iperf3LogLine('noise'));
expect(session.isEmpty, isTrue);
});
test('clear returns it to the empty state', () {
final session = Iperf3Session()
..add(const Iperf3Start(targetHost: 'host'))
..add(sample(0, 100))
..add(const Iperf3Error('boom'))
..add(const Iperf3Stopped())
..add(const Iperf3End())
..clear();
expect(session.isEmpty, isTrue);
expect(session.stopped, isFalse);
expect(session.hasError, isFalse);
expect(session.peakBitsPerSecond, isNull);
});
test('exposed collections are unmodifiable', () {
final session = Iperf3Session()..add(sample(0, 1));
expect(() => session.intervals.add(sample(1, 1)), throwsUnsupportedError);
expect(() => session.errors.add('x'), throwsUnsupportedError);
});
});
}
+62
View File
@@ -0,0 +1,62 @@
import 'dart:async';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
/// In-memory [Iperf3Runner] for tests.
///
/// Implements the interface rather than the concrete service, so it tracks the
/// five members the UI actually depends on instead of every public member the
/// MethodChannel implementation happens to have.
class FakeIperf3Runner implements Iperf3Runner {
FakeIperf3Runner({this.onRun});
/// Supplies the result for each [run], and may [emit] events first.
Future<Iperf3Result> Function(TestConfiguration)? onRun;
final StreamController<Iperf3Event> _controller =
StreamController<Iperf3Event>.broadcast();
/// Configurations passed to [run], in order.
final List<TestConfiguration> runCalls = [];
int stopCallCount = 0;
bool disposed = false;
bool _isRunning = false;
int get runCallCount => runCalls.length;
/// Push an event as if the native layer had reported it.
void emit(Iperf3Event event) => _controller.add(event);
@override
Stream<Iperf3Event> get events => _controller.stream;
@override
bool get isRunning => _isRunning;
@override
Future<Iperf3Result> run(TestConfiguration config) async {
runCalls.add(config);
_isRunning = true;
try {
if (onRun != null) return await onRun!(config);
return const Iperf3Result(rawOutput: '', exitCode: 0);
} finally {
_isRunning = false;
}
}
@override
Future<void> stop() async {
stopCallCount++;
}
@override
Future<void> dispose() async {
disposed = true;
await _controller.close();
}
}
@@ -0,0 +1,87 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/ui/features/test_run/iperf3_event_formatter.dart';
void main() {
group('formatBytes', () {
test('scales in binary units the way iperf3 prints totals', () {
expect(formatBytes(0), equals('0.00 Bytes'));
expect(formatBytes(1024), equals('1.00 KBytes'));
expect(formatBytes(98566144), equals('94.0 MBytes'));
expect(formatBytes(1073741824), equals('1.00 GBytes'));
});
});
group('formatBitrate', () {
test('scales in decimal units, as network rates are quoted', () {
expect(formatBitrate(0), equals('0.00 bits/sec'));
expect(formatBitrate(8000), equals('8.00 Kbits/sec'));
expect(formatBitrate(790000000), equals('790 Mbits/sec'));
expect(formatBitrate(2500000000), equals('2.50 Gbits/sec'));
});
});
group('formatIperf3Event', () {
test('renders a start event', () {
const event = Iperf3Start(
targetHost: '192.168.2.182',
targetPort: 5201,
protocol: 'TCP',
streamCount: 1,
durationSeconds: 10,
);
final text = formatIperf3Event(event)!;
expect(text, contains('Connecting to host 192.168.2.182, port 5201'));
expect(text, contains('TCP'));
expect(text, contains('1 stream'));
});
test('renders an interval with retransmits', () {
const event = Iperf3Interval(
start: 0,
end: 1,
bytes: 98566144,
bitsPerSecond: 788410352,
retransmits: 3,
);
final text = formatIperf3Event(event)!;
expect(text, contains('0.00-1.00 sec'));
expect(text, contains('94.0 MBytes'));
expect(text, contains('788 Mbits/sec'));
expect(text, contains('3 retr'));
});
test('marks an omitted interval', () {
const event = Iperf3Interval(
start: 0, end: 1, bytes: 1, bitsPerSecond: 8, omitted: true);
expect(formatIperf3Event(event), contains('omitted'));
});
test('renders sender and receiver lines from an end event', () {
const event = Iperf3End(
sent: Iperf3Total(
seconds: 10, bytes: 987654321, bitsPerSecond: 790000000,
retransmits: 7),
received: Iperf3Total(
seconds: 10.01, bytes: 987000000, bitsPerSecond: 789000000),
hostCpuPercent: 12.5,
);
final text = formatIperf3Event(event)!;
expect(text, contains('sender'));
expect(text, contains('receiver'));
expect(text, contains('7 retr'));
expect(text, contains('CPU: local 12.5%'));
});
test('renders errors and stops', () {
expect(formatIperf3Event(const Iperf3Error('boom')),
equals('iperf3 error: boom'));
expect(formatIperf3Event(const Iperf3Stopped()),
equals('Stopped by user.'));
});
test('drops an empty log line', () {
expect(formatIperf3Event(const Iperf3LogLine('')), isNull);
});
});
}
@@ -1,39 +1,40 @@
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart';
import '../../../support/fake_iperf3_runner.dart';
void main() {
group('TestRunViewModel', () {
test('starts in idle state', () {
final vm = TestRunViewModel(
iperf3Service: _MockIperf3Service(),
);
final vm = TestRunViewModel(iperf3Service: FakeIperf3Runner());
expect(vm.state, equals(TestRunState.idle));
expect(vm.result, isNull);
expect(vm.currentConfig, isNull);
expect(vm.output, isEmpty);
});
test('can be reset', () {
final vm = TestRunViewModel(
iperf3Service: _MockIperf3Service(),
);
final vm = TestRunViewModel(iperf3Service: FakeIperf3Runner());
vm.reset();
expect(vm.state, equals(TestRunState.idle));
});
test('invalid config sets error state without running', () async {
final vm = TestRunViewModel(
iperf3Service: _MockIperf3Service(),
);
final mock = FakeIperf3Runner();
final vm = TestRunViewModel(iperf3Service: mock);
const config = TestConfiguration(server: '');
await vm.runTest(config);
expect(vm.state, equals(TestRunState.error));
expect(mock.runCallCount, equals(0));
});
test('runTest with valid config calls service', () async {
final mock = _MockIperf3Service(
final mock = FakeIperf3Runner(
onRun: (config) async {
expect(config.server, equals('192.168.1.1'));
return const Iperf3Result(rawOutput: 'done', exitCode: 0);
@@ -45,17 +46,161 @@ void main() {
expect(vm.state, equals(TestRunState.completed));
expect(vm.result, isNotNull);
});
test('non-zero exit code lands in error state', () async {
final mock = FakeIperf3Runner(
onRun: (_) async =>
const Iperf3Result(rawOutput: 'boom', exitCode: 1),
);
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.state, equals(TestRunState.error));
expect(vm.result!.exitCode, equals(1));
});
test('streamed events accumulate into output during the run', () async {
final mock = FakeIperf3Runner();
mock.onRun = (_) async {
mock.emit(const Iperf3Start(targetHost: '192.168.1.1', targetPort: 5201));
mock.emit(const Iperf3Interval(
start: 0,
end: 1,
bytes: 11744051,
bitsPerSecond: 94100000,
));
// Let the broadcast stream deliver before the run resolves.
await Future<void>.delayed(Duration.zero);
return const Iperf3Result(rawOutput: 'ignored', exitCode: 0);
};
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.output, contains('Connecting to host 192.168.1.1'));
expect(vm.output, contains('94.1 Mbits/sec'));
// The streamed transcript wins over the service's own copy.
expect(vm.output, isNot(contains('ignored')));
});
test('events are fed into the session', () async {
final mock = FakeIperf3Runner();
mock.onRun = (_) async {
mock.emit(const Iperf3Interval(
start: 0, end: 1, bytes: 1000, bitsPerSecond: 8000));
mock.emit(const Iperf3End(
sent: Iperf3Total(seconds: 1, bytes: 1000, bitsPerSecond: 8000),
));
await Future<void>.delayed(Duration.zero);
return const Iperf3Result(rawOutput: '', exitCode: 0);
};
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.session.sampleCount, equals(1));
expect(vm.session.summary, isNotNull);
});
test('a new run clears the previous session', () async {
final mock = FakeIperf3Runner();
mock.onRun = (_) async {
mock.emit(const Iperf3Interval(
start: 0, end: 1, bytes: 1000, bitsPerSecond: 8000));
await Future<void>.delayed(Duration.zero);
return const Iperf3Result(rawOutput: '', exitCode: 0);
};
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.session.sampleCount, equals(1));
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.session.sampleCount, equals(1),
reason: 'not appended to the old run');
});
test('falls back to result output when nothing streamed', () async {
final mock = FakeIperf3Runner(
onRun: (_) async =>
const Iperf3Result(rawOutput: 'summary only', exitCode: 0),
);
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.output, contains('summary only'));
});
test('output is cleared when a new run starts', () async {
final mock = FakeIperf3Runner(
onRun: (_) async =>
const Iperf3Result(rawOutput: 'first', exitCode: 0),
);
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.output, contains('first'));
mock.onRun = (_) async =>
const Iperf3Result(rawOutput: 'second', exitCode: 0);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.output, isNot(contains('first')));
expect(vm.output, contains('second'));
});
test('stopTest forwards to the service only while running', () async {
final mock = FakeIperf3Runner();
final vm = TestRunViewModel(iperf3Service: mock);
await vm.stopTest();
expect(mock.stopCallCount, equals(0), reason: 'idle: nothing to stop');
final gate = Completer<Iperf3Result>();
mock.onRun = (_) => gate.future;
final run = vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.isRunning, isTrue);
await vm.stopTest();
expect(mock.stopCallCount, equals(1));
gate.complete(const Iperf3Result(rawOutput: 'stopped', exitCode: 0));
await run;
});
test('wasStopped reflects a user-requested stop', () async {
final mock = FakeIperf3Runner(
onRun: (_) async => const Iperf3Result(
rawOutput: 'stopped early',
exitCode: 0,
stopped: true,
),
);
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
// A user stop is a normal outcome: completed, not error.
expect(vm.state, equals(TestRunState.completed));
expect(vm.wasStopped, isTrue);
});
test('wasStopped is false for a run that finished on its own', () async {
final mock = FakeIperf3Runner(
onRun: (_) async =>
const Iperf3Result(rawOutput: 'done', exitCode: 0),
);
final vm = TestRunViewModel(iperf3Service: mock);
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(vm.wasStopped, isFalse);
});
test('a second runTest while running is ignored', () async {
final mock = FakeIperf3Runner();
final gate = Completer<Iperf3Result>();
mock.onRun = (_) => gate.future;
final vm = TestRunViewModel(iperf3Service: mock);
final run = vm.runTest(const TestConfiguration(server: '192.168.1.1'));
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
expect(mock.runCallCount, equals(1));
gate.complete(const Iperf3Result(rawOutput: 'done', exitCode: 0));
await run;
});
});
}
class _MockIperf3Service implements Iperf3Service {
_MockIperf3Service({this.onRun});
final Future<Iperf3Result> Function(TestConfiguration)? onRun;
@override
Future<Iperf3Result> run(TestConfiguration config) async {
if (onRun != null) return onRun!(config);
return const Iperf3Result(rawOutput: '', exitCode: 0);
}
}
+78 -6
View File
@@ -1,17 +1,89 @@
// This is a basic Flutter widget test.
// Widget-level smoke tests.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
// These drive the real UI against a FakeIperf3Runner: no MethodChannel, no
// native library, no device. That is only possible because the presentation
// layer depends on the Iperf3Runner interface rather than on Iperf3Service.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3gui/domain/models/iperf3_event.dart';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/main.dart';
import 'support/fake_iperf3_runner.dart';
void main() {
testWidgets('App smoke test', (WidgetTester tester) async {
await tester.pumpWidget(const Iperf3GuiApp());
await tester.pumpWidget(Iperf3GuiApp(runner: FakeIperf3Runner()));
expect(find.text('iperf3-gui'), findsOneWidget);
expect(find.text('GO'), findsOneWidget);
expect(find.text('Ready'), findsOneWidget);
});
testWidgets('runs a test and renders streamed results', (tester) async {
final runner = FakeIperf3Runner();
runner.onRun = (config) async {
runner.emit(Iperf3Start(
targetHost: config.server,
targetPort: config.port,
protocol: 'TCP',
streamCount: config.parallelStreams,
));
runner.emit(const Iperf3Interval(
start: 0,
end: 1,
bytes: 98566144,
bitsPerSecond: 788410352,
));
await Future<void>.delayed(Duration.zero);
return const Iperf3Result(rawOutput: '', exitCode: 0);
};
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
await tester.tap(find.text('GO'));
await tester.pumpAndSettle();
expect(runner.runCallCount, equals(1));
expect(find.text('Test complete'), findsOneWidget);
expect(find.textContaining('788 Mbits/sec'), findsWidgets);
// The stat row is driven by the parsed interval series.
expect(find.text('peak'), findsOneWidget);
});
testWidgets('shows a stop button while running and can stop', (tester) async {
final runner = FakeIperf3Runner();
final gate = Completer<Iperf3Result>();
runner.onRun = (_) => gate.future;
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
await tester.tap(find.text('GO'));
await tester.pump();
expect(find.text('STOP'), findsOneWidget);
expect(find.text('Running…'), findsOneWidget);
await tester.tap(find.text('STOP'));
await tester.pump();
expect(runner.stopCallCount, equals(1));
gate.complete(const Iperf3Result(
rawOutput: '', exitCode: 0, stopped: true));
await tester.pumpAndSettle();
expect(find.text('Stopped'), findsOneWidget);
});
testWidgets('rejects an empty server address', (tester) async {
final runner = FakeIperf3Runner();
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
await tester.enterText(find.byType(TextField).first, '');
await tester.tap(find.text('GO'));
await tester.pumpAndSettle();
expect(runner.runCallCount, equals(0));
expect(find.textContaining('Invalid configuration'), findsOneWidget);
});
}
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Assert that R8 left the names JNI resolves at runtime alone.
#
# iperf_jni.c looks up the callback methods by name with GetMethodID. R8 renames
# by default, and a renamed callback does not fail the build — it produces an
# APK that aborts on the first callback of every test:
#
# NoSuchMethodError: no non-static method "Lp0;.onOutput(Ljava/lang/String;)V"
#
# That shipped once. Building the release APK is not enough to catch it, because
# the build succeeds; this reads R8's own mapping file and checks the names
# survived. Run after `flutter build apk --release`.
set -euo pipefail
MAPPING="${1:-build/app/outputs/mapping/release/mapping.txt}"
APK="${2:-build/app/outputs/flutter-apk/app-release.apk}"
if [[ ! -f "$MAPPING" ]]; then
echo "FAIL: no R8 mapping at $MAPPING"
echo " Run 'flutter build apk --release' first. If the release build"
echo " no longer minifies, this check is meaningless — say so"
echo " explicitly rather than deleting it."
exit 1
fi
failed=0
# A class kept by name maps to itself: "a.b.C -> a.b.C:".
require_class_kept() {
local fqcn="$1"
if grep -qF -- "$fqcn -> $fqcn:" "$MAPPING"; then
echo " ok class $fqcn"
else
echo " FAIL class $fqcn was renamed or removed"
grep -F -- "$fqcn ->" "$MAPPING" | head -3 | sed 's/^/ got: /' || true
failed=1
fi
}
# A method kept by name ends with "-> <name>".
require_method_kept() {
local signature="$1" name="$2"
if grep -qE -- "$signature.*-> ${name}\$" "$MAPPING"; then
echo " ok method $name"
else
echo " FAIL method $name was renamed"
grep -E -- "$signature" "$MAPPING" | head -3 | sed 's/^/ got: /' || true
failed=1
fi
}
echo "Checking JNI-visible names in $MAPPING"
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner'
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner$IperfCallback'
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner$MainThreadCallback'
# Resolved by iperf_jni.c on the concrete callback object's class.
require_method_kept 'void onOutput\(java\.lang\.String\)' 'onOutput'
require_method_kept 'void onError\(java\.lang\.String\)' 'onError'
require_method_kept 'void onComplete\(\)' 'onComplete'
# Native methods are NOT checked against mapping.txt: R8 only emits entries for
# members it has line-number info or a rename for, and a native method has no
# body, so it is absent whether or not it was renamed. Absence would prove
# nothing. Check the DEX string pool instead — a surviving name is interned
# there, a renamed one is not.
check_dex_names() {
local apk="$1"
shift
if [[ ! -f "$apk" ]]; then
echo " skip DEX name check: no APK at $apk"
return
fi
local tmp
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' RETURN
if ! unzip -o -q "$apk" 'classes*.dex' -d "$tmp" 2>/dev/null; then
echo " FAIL could not extract classes*.dex from $apk"
failed=1
return
fi
# Dump the string pool once to a file, then grep the file. Never
# `... | strings | grep -q`: grep -q exits at the first match, SIGPIPEs
# strings, and `set -o pipefail` reports the whole pipeline as failed — so
# a name found early "fails" and one found late passes. That flakiness cost
# an afternoon; leave the intermediate file in place.
cat "$tmp"/classes*.dex | strings > "$tmp/dex-strings.txt"
local name
for name in "$@"; do
if grep -qxF -- "$name" "$tmp/dex-strings.txt"; then
echo " ok native $name"
else
echo " FAIL native $name is absent from the DEX string pool"
failed=1
fi
done
}
check_dex_names "${2:-build/app/outputs/flutter-apk/app-release.apk}" \
runIperfLive forceStopIperfTest setTempDir isTestRunning
if [[ $failed -ne 0 ]]; then
echo
echo "R8 renamed something JNI resolves by name. The release APK will abort"
echo "on the first callback. Fix android/app/proguard-rules.pro."
exit 1
fi
echo "All JNI-visible names survived minification."
+288
View File
@@ -0,0 +1,288 @@
#!/usr/bin/env bash
# Provision this machine as a Gitea Actions runner for the android-stuff org.
#
# sudo ./tool/setup_ci_runner.sh --check # pre-flight only, changes nothing
# sudo RUNNER_TOKEN=xxxx ./tool/setup_ci_runner.sh
#
# Get an ORG-scoped token from
# <gitea>/org/android-stuff/settings/actions/runners → "Create new runner"
# Org scope, not repo scope, so every android-stuff repo shares one runner.
#
# ── WHY THIS MACHINE, NOT THE GITEA HOST ────────────────────────────────────
# Gitea runs on an aarch64 box. Flutter cannot build an Android APK on a Linux
# arm64 host at all: the SDK ships no arm64-host gen_snapshot
# (flutter/flutter#189724, #75864, open as of July 2026). An x86_64 runner is a
# requirement here, not a preference.
#
# ── WHY HOST MODE, NOT A CONTAINER ──────────────────────────────────────────
# This machine has a working Flutter + Android SDK + NDK. In host mode jobs use
# it directly, so CI builds with the same toolchain that produced the verified
# release and skips a per-run NDK download. The cost is that there is no
# container boundary — which is exactly why the service account below matters.
#
# ── WHY A DEDICATED USER, AND A SYSTEM SERVICE ──────────────────────────────
# Host mode means the service account IS the sandbox; there is no other. A
# workflow file is code, and it runs as whoever this service runs as. Running
# as a human user would give every workflow read access to that user's home,
# SSH keys included. Running as root would be worse, and would also miss the
# warm Gradle/pub caches in that home (HOME=/root), re-downloading dependencies
# every run for no benefit.
#
# So: a dedicated unprivileged account, in the flutterdev group for SDK access,
# with its own HOME so its caches are its own. A *system* service rather than a
# user service, so it starts at boot under plain systemctl with no lingering
# session — the service being root-managed is unrelated to the jobs being
# unprivileged, and conflating those two was the earlier mistake here.
set -euo pipefail
VERSION="${RUNNER_VERSION:-3.3.0}"
INSTANCE="${GITEA_INSTANCE:-http://pi5-16.local:3005}"
RUNNER_USER="${RUNNER_USER:-gitea-runner}"
SDK_GROUP="${SDK_GROUP:-flutterdev}"
RUNNER_HOME="${RUNNER_HOME:-/var/lib/gitea-runner}"
BIN="${RUNNER_BIN:-/usr/local/bin/gitea-runner}"
UNIT="/etc/systemd/system/gitea-runner.service"
RUNNER_NAME="${RUNNER_NAME:-$(hostname)-android}"
# Capability label, deliberately not a hostname: it says what the runner can
# do, so a workflow does not encode which machine happens to provide it.
LABEL="${RUNNER_LABEL:-flutter-android}"
CHECK_ONLY=0
[[ "${1:-}" == "--check" ]] && CHECK_ONLY=1
[[ $EUID -eq 0 ]] || { echo "Run with sudo: sudo RUNNER_TOKEN=... $0" >&2; exit 1; }
# --check is for validating this machine before committing to anything, so it
# deliberately does not require a token.
if [[ $CHECK_ONLY -eq 0 ]]; then
: "${RUNNER_TOKEN:?set RUNNER_TOKEN to an org registration token (see header)}"
fi
say() { printf '\n==> %s\n' "$1"; }
# ── Pre-flight, before anything is created ─────────────────────────────────
say "Pre-flight"
missing=0
for cmd in flutter dart java cmake ninja node git unzip curl; do
# Resolved from systemd's own PATH, not an interactive shell's, since that
# is what the service will actually have.
if PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
command -v "$cmd" >/dev/null; then
printf ' ok %s\n' "$cmd"
else
printf ' MISSING %s (not on systemd default PATH)\n' "$cmd"
missing=1
fi
done
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/opt/android-sdk}}"
for d in "$SDK/ndk" "$SDK/build-tools" "$SDK/platforms"; do
[[ -d "$d" ]] && printf ' ok %s\n' "$d" \
|| { printf ' MISSING %s\n' "$d"; missing=1; }
done
getent group "$SDK_GROUP" >/dev/null \
&& printf ' ok group %s\n' "$SDK_GROUP" \
|| { printf ' MISSING group %s\n' "$SDK_GROUP"; missing=1; }
if [[ $missing -ne 0 ]]; then
echo
echo "Refusing to register: a runner advertising '$LABEL' must actually" >&2
echo "provide these, or every job fails confusingly instead of here." >&2
exit 1
fi
if [[ $CHECK_ONLY -eq 1 ]]; then
say "Pre-flight passed"
echo " Nothing was created. Re-run with RUNNER_TOKEN set to provision."
echo " Note: SDK write access is verified as $RUNNER_USER, which can only"
echo " be checked once that account exists — so it is not covered here."
exit 0
fi
# ── Service account ────────────────────────────────────────────────────────
say "Service account: $RUNNER_USER"
if id "$RUNNER_USER" &>/dev/null; then
echo " exists; ensuring membership of $SDK_GROUP"
usermod -aG "$SDK_GROUP" "$RUNNER_USER"
else
# --shell /bin/bash, not nologin: host-mode steps are executed as shell
# scripts, and a login shell costs nothing here — the account has no
# password, so it is only reachable via root anyway.
useradd --system \
--create-home --home-dir "$RUNNER_HOME" \
--shell /bin/bash \
--groups "$SDK_GROUP" \
--comment "Gitea Actions runner ($LABEL)" \
"$RUNNER_USER"
echo " created"
fi
install -d -o "$RUNNER_USER" -g "$SDK_GROUP" -m 0750 \
"$RUNNER_HOME" "$RUNNER_HOME/workspace"
id "$RUNNER_USER"
# Group membership is worthless if the ACLs disagree, so prove it rather than
# assume it — as the service user, against the paths a build actually writes.
#
# By actually writing a file, not by asking `test -w`. This machine's
# /usr/bin/test is uutils coreutils 0.8.0, whose -w does not credit
# supplementary group membership: it reports /opt/android-sdk unwritable for
# any account whose write access comes from the flutterdev group rather than
# from the owner bits — which is every account that should be running builds
# here. (-r and -x pass only because `other` has r-x.) bash's builtin `[ -w ]`
# gets it right, but sudo execs the external binary, so the builtin is not
# available here. A write probe is immune to the whole question.
say "Verifying $RUNNER_USER can write the SDKs"
write_ok=1
for d in "/opt/flutter/bin/cache" "$SDK"; do
if sudo -u "$RUNNER_USER" env probe="$d/.gitea-runner-probe.$$" \
sh -c 'touch "$probe" && rm -f "$probe"' 2>/dev/null; then
printf ' ok writable: %s\n' "$d"
else
printf ' FAIL not writable: %s\n' "$d"
sudo -u "$RUNNER_USER" rm -f "$d/.gitea-runner-probe.$$" 2>/dev/null || true
write_ok=0
fi
done
if [[ $write_ok -ne 1 ]]; then
echo
echo "$RUNNER_USER is in $SDK_GROUP but still cannot write. Check:" >&2
echo " getfacl /opt/flutter /opt/android-sdk" >&2
echo " id $RUNNER_USER" >&2
exit 1
fi
# ── Binary ─────────────────────────────────────────────────────────────────
say "Installing gitea-runner $VERSION"
if [[ -x "$BIN" ]] && "$BIN" --version 2>/dev/null | grep -qF "$VERSION"; then
echo " already at $VERSION"
else
url="https://dl.gitea.com/gitea-runner/${VERSION}/gitea-runner-${VERSION}-linux-amd64"
echo " $url"
curl -fSL --retry 3 -o "$BIN.tmp" "$url"
chmod 0755 "$BIN.tmp"
mv "$BIN.tmp" "$BIN"
fi
"$BIN" --version
# ── Config ─────────────────────────────────────────────────────────────────
say "Config"
CONFIG="$RUNNER_HOME/config.yaml"
if [[ -f "$CONFIG" && -f "$RUNNER_HOME/.runner" ]]; then
# Registered: this config is in use, do not touch it.
echo " $CONFIG exists and the runner is registered — leaving it alone"
else
if [[ -f "$CONFIG" ]]; then
echo " $CONFIG exists but nothing is registered yet — rewriting it"
fi
cat > "$CONFIG" <<YAML
log:
level: info
runner:
# Absolute on purpose: a relative path resolves against whatever directory
# the command was invoked from, which for `register` is wherever the admin
# happened to be standing — typically a repo this account cannot write.
file: ${RUNNER_HOME}/.runner
# One at a time: a 2-ABI NDK compile plus Gradle will saturate this box, and
# concurrent jobs would only contend for it.
capacity: 1
timeout: 45m
# ':host' runs steps directly on this machine as ${RUNNER_USER}, using the
# toolchain verified in pre-flight, rather than in a container that would
# have to fetch its own.
labels:
- "${LABEL}:host"
container:
# Unused in host mode. Kept so switching to container mode later is an edit
# rather than a rediscovery — it would need the Docker network that the
# Gitea host's own runner also needs.
network: ""
privileged: false
host:
workdir_parent: ${RUNNER_HOME}/workspace
YAML
chown "$RUNNER_USER:$SDK_GROUP" "$CONFIG"
chmod 0640 "$CONFIG"
echo " wrote $CONFIG"
fi
# ── Registration ───────────────────────────────────────────────────────────
say "Registering with $INSTANCE"
if [[ -f "$RUNNER_HOME/.runner" ]]; then
echo " already registered ($RUNNER_HOME/.runner) — skipping."
echo " To re-register: delete that file AND remove the runner in Gitea."
else
# Run from the runner's own directory: register writes .runner and
# .runner.lock relative to cwd unless told otherwise, and this account
# cannot write wherever the admin invoked the script from.
# --labels is deliberately absent: gitea-runner ignores it when the config
# file defines labels, and warns that it is doing so.
sudo -u "$RUNNER_USER" env HOME="$RUNNER_HOME" sh -c '
cd "$1" || exit 1
exec "$2" register --no-interactive \
--instance "$3" --token "$4" --name "$5" --config "$6"
' sh "$RUNNER_HOME" "$BIN" "$INSTANCE" "$RUNNER_TOKEN" "$RUNNER_NAME" "$CONFIG"
# register writes .runner into the cwd it was given; make sure it landed.
[[ -f "$RUNNER_HOME/.runner" ]] || {
echo " registration produced no .runner in $RUNNER_HOME" >&2
exit 1
}
fi
# ── Service ────────────────────────────────────────────────────────────────
say "systemd unit"
cat > "$UNIT" <<UNITFILE
[Unit]
Description=Gitea Actions runner (${LABEL}, host mode)
Documentation=https://gitea.com/gitea/act_runner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${RUNNER_USER}
Group=${SDK_GROUP}
WorkingDirectory=${RUNNER_HOME}
ExecStart=${BIN} daemon --config ${CONFIG}
Restart=always
RestartSec=5
# HOME must be explicit: it decides where Gradle and pub put their caches, and
# a service does not inherit a login environment.
Environment=HOME=${RUNNER_HOME}
Environment=GRADLE_USER_HOME=${RUNNER_HOME}/.gradle
Environment=PUB_CACHE=${RUNNER_HOME}/.pub-cache
Environment=ANDROID_HOME=${SDK}
Environment=ANDROID_SDK_ROOT=${SDK}
[Install]
WantedBy=multi-user.target
UNITFILE
echo " wrote $UNIT"
systemctl daemon-reload
systemctl enable --now gitea-runner
sleep 2
systemctl --no-pager --lines=15 status gitea-runner || true
cat <<EOF
==> Done.
Logs: journalctl -u gitea-runner -f
Stop: systemctl stop gitea-runner
Remove: systemctl disable --now gitea-runner
rm $UNIT $BIN && rm -rf $RUNNER_HOME
userdel $RUNNER_USER
...and delete the runner in the Gitea org settings.
The runner should now appear under
${INSTANCE}/org/android-stuff/settings/actions/runners
with label '$LABEL'. The queued run for this repo should start on its own.
First Android build will be slow: this account's Gradle and pub caches
start empty by design. Subsequent runs reuse them.
EOF
-1
View File
@@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)