icmpScan method

Stream<Host> icmpScan(
  1. String subnet, {
  2. int firstIP = 1,
  3. int lastIP = 255,
  4. int scanThreads = 10,
  5. Duration timeout = const Duration(seconds: 1),
  6. ProgressCallback? progressCallback,
})

Discovers network devices in the given subnet.

This method uses ICMP to ping the network. It may be slow to complete the whole scan, so it is possible to provide a range to scan.

scanThreads determines the number of threads to spawn for the scan. Avoid using high numbers of threads, as it may cause high memory usage.

Consider using quickIcmpScanSync or quickIcmpScanAsync.

Implementation

Stream<Host> icmpScan(
  String subnet, {
  int firstIP = 1,
  int lastIP = 255,
  int scanThreads = 10,
  Duration timeout = const Duration(seconds: 1),
  ProgressCallback? progressCallback,
}) {
  late StreamController<Host> controller;
  final isolateInstances = scanThreads;
  final numOfHostsToPing = lastIP - firstIP + 1;
  final rangeForEachIsolate = (numOfHostsToPing / isolateInstances).round();
  final isolatesList = <Isolate>[];

  var numOfHostsPinged = 0;

  // Check for possible errors in the configuration
  assert(
    firstIP >= 1 && firstIP <= lastIP,
    'firstIP must be between 1 and lastIP',
  );

  assert(
    scanThreads >= 1,
    'Scan threads must be at least 1',
  );

  if (_isScanInProgress) {
    throw Exception(
      'Cannot begin scanning while the first one is still running',
    );
  }

  Future<void> startScan() async {
    _isScanInProgress = true;

    for (var currIP = firstIP; currIP < 255; currIP += rangeForEachIsolate) {
      final receivePort = ReceivePort();
      final fromIP = currIP;
      final toIP = (currIP + rangeForEachIsolate - 1).clamp(firstIP, lastIP);
      final isolateArgs = [
        subnet,
        fromIP,
        toIP,
        timeout.inSeconds,
        receivePort.sendPort,
      ];

      final isolate = await Isolate.spawn(
        _icmpRangeScan,
        isolateArgs,
        debugName: 'ScanThread: $fromIP - $toIP',
      );
      isolatesList.add(isolate);

      receivePort.listen((msg) {
        msg = msg as List;

        final hostToPing = msg.elementAt(0) as String;
        final isReachable = msg.elementAt(1) as bool;
        final pingTime = msg.elementAtOrNull(2) as int?;

        numOfHostsPinged++;
        final progress =
            (numOfHostsPinged / numOfHostsToPing).toStringAsFixed(2);
        progressCallback?.call(double.parse(progress));

        if (numOfHostsPinged == numOfHostsToPing) {
          if (debugLogging) {
            log('Scan finished');
          }
          _isScanInProgress = false;
          controller.close();
        }

        if (isReachable) {
          controller.add(
            Host(
              internetAddress: InternetAddress(hostToPing),
              pingTime:
                  pingTime != null ? Duration(milliseconds: pingTime) : null,
            ),
          );
        }
      });
    }
  }

  void stopScan() {
    for (final isolate in isolatesList) {
      isolate.kill(priority: Isolate.immediate);
    }

    _isScanInProgress = false;
    controller.close();
  }

  controller = StreamController<Host>(
    onCancel: stopScan,
    onListen: startScan,
    onPause: stopScan,
    onResume: startScan,
  );

  return controller.stream;
}