Files
iperf3-gui/android/app/src/main/kotlin/com/novoyuuparosk/iperf3gui/IperfRunner.kt
T
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

121 lines
4.7 KiB
Kotlin

package com.novoyuuparosk.iperf3gui
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
/**
* Kotlin binding layer for native iperf3 via JNI.
*
* This class loads the native library (libiperf3jni.so) and exposes
* iperf3 functionality to Flutter via MethodChannel callbacks.
*
* iperf3 runs natively inside the app process — no Process.start() needed,
* no SELinux restrictions. Output is streamed live to the Flutter UI via
* the IperfCallback interface.
*/
object IperfRunner : FlutterPlugin {
private var channel: MethodChannel? = null
init {
// Load native library at class initialization
try {
android.util.Log.i("IperfRunner", "Loading native library...")
System.loadLibrary("iperf3jni")
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) {
channel = MethodChannel(binding.binaryMessenger, "com.novoyuuparosk.iperf3gui/iperf")
channel?.setMethodCallHandler { call, result ->
when (call.method) {
"runIperf" -> {
val args = call.argument<List<String>>("args")?.toTypedArray()
if (args != null) {
runIperfLive(args, object : IperfCallback {
override fun onOutput(line: String) {
channel?.invokeMethod("onOutput", mapOf("output" to line))
}
override fun onError(error: String) {
channel?.invokeMethod("onError", mapOf("error" to error))
}
override fun onComplete() {
channel?.invokeMethod("onComplete", emptyMap<String, Any>())
}
})
result.success(null)
} else {
result.error("INVALID_ARGS", "args must be a String array", null)
}
}
"stopIperf" -> {
forceStopIperfTest(object : IperfCallback {
override fun onOutput(line: String) {
channel?.invokeMethod("onOutput", mapOf("output" to line))
}
override fun onError(error: String) {
channel?.invokeMethod("onError", mapOf("error" to error))
}
override fun onComplete() {
channel?.invokeMethod("onComplete", emptyMap<String, Any>())
}
})
result.success(null)
}
"isTestRunning" -> {
result.success(isTestRunning())
}
else -> {
result.notImplemented()
}
}
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = null
}
// ─────────────────────────────────────────────────────────────────
// Native method bindings
// ─────────────────────────────────────────────────────────────────
/**
* Start an iperf3 test with the given arguments.
* This calls the native C function via JNI.
*/
external fun runIperfLive(arguments: Array<String>, callback: IperfCallback)
/**
* Gracefully stop a running iperf3 test.
*/
external fun forceStopIperfTest(callback: IperfCallback)
/**
* Check if an iperf3 test is currently running.
* @return true if a test is in progress, false otherwise
*/
external fun isTestRunning(): Boolean
/**
* Callback interface for iperf3 test output.
*
* Used by the native layer to stream test results back to Flutter
* in real-time.
*/
interface IperfCallback {
/** Called with each line of iperf3 output (stdout/stderr) */
fun onOutput(line: String)
/** Called when an error occurs */
fun onError(error: String)
/** Called when the test completes (success or failure) */
fun onComplete()
}
}