flutter_netdiag_plus

Detailed network diagnostics for Flutter: DNS lookup, TCP connect, HTTP RTT, multi-target ping, traceroute. All measurements use pure dart:io — only traceroute needs native code, and that code ships pre-packaged with the plugin and builds automatically on flutter pub get, no manual Xcode/Gradle steps required.

Install

dependencies:
  flutter_netdiag_plus: ^0.1.0
flutter pub get

Permissions

Android — the package automatically merges the INTERNET permission into the app that uses it, nothing else to declare.

iOS — nothing to declare. Socket.connect doesn't go through ATS (ATS only governs NSURLSession). Only pinging a LAN host requires adding NSLocalNetworkUsageDescription to the app's Info.plist.

macOS — the app needs to enable com.apple.security.network.client in macos/Runner/*.entitlements if it uses App Sandbox.

Usage

import 'package:flutter_netdiag_plus/flutter_netdiag_plus.dart';

NetworkDiagnosticPanel(
  service: NetworkDiagnosticService(
    host: 'api.example.com',
    port: 443,
    externalTargets: const [
      PingTarget(id: 'google', label: 'Google', host: 'google.com'),
      PingTarget(id: 'cloudflare', label: 'Cloudflare', host: 'cloudflare.com'),
    ],
  ),
)

No UI needed — fits a "Report network issue" button, attach report.toJson() to the ticket:

final report = await NetworkDiagnosticService(host: 'api.example.com').runOnce();
await api.sendTicket(diagnostics: report.toJson());

Some design decisions

Ping via TCP connect, not ICMP. Raw ICMP sockets need native code, Android 10+ restricts them, and many firewalls drop ICMP while still letting TCP 443 through — meaning ICMP reports a false failure while the network is actually fine. TCP handshake time is also close to the real latency an app feels, since the app itself talks over TCP/TLS, not ICMP.

Takes the min, not the average. Network noise can only make a measurement higher, so the fastest of 3 attempts is the closest estimate of the true latency. An average gets skewed by a single spike.

DNS will read ~0ms from the 2nd measurement on. InternetAddress.lookup goes through the OS resolver, which caches results. For a true reading, swap in a direct DoH query to 1.1.1.1 / 8.8.8.8 — the spot to change already has a comment in the service.

Traceroute

Plug it in via hopResolver:

NetworkDiagnosticService(
  host: 'api.example.com',
  hopResolver: (host) async {
    final result = await createTraceroute().trace(host);
    return result.hops.isEmpty ? null : result.hopCount;
  },
)

Or use it standalone:

final result = await createTraceroute().trace('example.com');
for (final hop in result.hops) {
  print(hop);          // "3  10.1.2.3  12.3ms"  or  "4  *"
}

reachedDestination == false means maxHops ran out before reaching the target — the hop count is then just a lower bound, and the UI shows "≥ N hops".

Platform-specific approach

Platform Approach
Android loops ping -c1 -t <ttl>, falls back to native NDK (ICMP ping-socket) if exec is blocked
Linux / macOS calls traceroute -n directly
Windows calls tracert -d
iOS native Swift (UDP probe + passive ICMP socket)

iOS: UDP probe + passive ICMP

  1. Sends a UDP probe to port 33434+ttl, with increasing IP_TTL.
  2. A separate, receive-only ICMP socket picks up the ICMP Time Exceeded reply.
  3. Matches the reply to the probe using the destination port embedded in the nested payload.

Doesn't use the ICMP Echo + IP_TTL variant used on Linux, because setsockopt(IP_TTL) on Darwin's SOCK_DGRAM ICMP socket behaves inconsistently, and matching a reply would then have to rely on the nested payload content — error-prone. With UDP, the destination port is the probe's id, so matching is exact.

Android: ping -t, fallback to NDK when exec is blocked

Android has no traceroute binary, but toybox's ping has a -t TTL flag — sending increasing TTLs and reading the ICMP Time Exceeded replies is the traceroute algorithm.

Some ROMs (mostly Chinese ROMs) block execve at the SELinux/seccomp layer — Process.run('ping', ...) throws a ProcessException right away. The package detects this automatically via a loopback self-ping, and falls back to the C code in android/src/main/cpp/traceroute.c when blocked:

  • Opens an ICMP ping-socket (socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) — not a raw socket, so no root needed; Android allows it via the net.ipv4.ping_group_range sysctl.
  • Increases TTL via setsockopt(IP_TTL).
  • Reads the ICMP Time Exceeded from routers along the path via recvmsg(fd, &msg, MSG_ERRQUEUE) after enabling IP_RECVERR — this API isn't exposed by android.system.Os in the Android SDK, so it has to be written natively (NDK/JNI); it can't be done in pure Kotlin.

Builds automatically via Gradle (externalNativeBuild { cmake { ... } }), no extra steps needed.

Known limitations

  • A * hop is normal. Many routers deliberately don't reply with ICMP Time Exceeded, or rate-limit it. Every traceroute implementation runs into this — it isn't a bug.
  • Slow. 30 hops × 1.5s is a worst case of ~45 seconds. If you only need the hop count, lowering maxHops to 20 is enough for most domestic targets.
  • Measuring over mobile data can report fewer hops than reality — the carrier's CGNAT hides some hops.
  • IPv4 only. An IPv6 target will fail to resolve.

Customizing the UI

All colors live in DiagTheme:

NetworkDiagnosticPanel(
  service: service,
  theme: const DiagTheme(
    background: Color(0xFF12161A),
    success: Color(0xFF22C55E),
    radius: 12,
  ),
)

Example

See example/ in the repo for a full demo app (host input field + panel + copy JSON).

Libraries

flutter_netdiag_plus
Đo lường mạng chi tiết cho Flutter — DNS lookup, TCP connect, HTTP RTT, ping nhiều đích, traceroute. Toàn bộ phần đo dùng dart:io thuần, chỉ traceroute cần code native (Android NDK, iOS Swift) đi kèm sẵn trong package — không cần thao tác thủ công gì thêm sau khi flutter pub get.
network_diagnostic/network_diagnostic_models
Models cho module đo lường mạng.
network_diagnostic/network_diagnostic_panel
Widget hiển thị bảng "Đo lường chi tiết".
network_diagnostic/network_diagnostic_service
Đo lường mạng chi tiết bằng dart:io thuần — không cần package ngoài.
network_diagnostic/traceroute/traceroute
Traceroute đa nền tảng.
network_diagnostic/traceroute/traceroute_android_native
Traceroute Android qua NDK — dùng khi exec binary ping bị chặn (một số ROM Trung Quốc siết execve, xem PingTtlTraceroute._execAvailable ở traceroute_process.dart).
network_diagnostic/traceroute/traceroute_ios
Traceroute trên iOS — gọi sang Swift qua MethodChannel.
network_diagnostic/traceroute/traceroute_process
Traceroute bằng cách gọi binary hệ thống — Android, Linux, macOS, Windows.