icmpScan method Null safety

Stream<HostModel> 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.

Implementation

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

  int numOfHostsPinged = 0;

  // Check for possible errors in the configuration
  if (firstIP > lastIP) {
    throw Exception("firstIP can't be larger than lastIP");
  }

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

  if (scanThreads < 1) {
    throw Exception('Scan threads must be at least 1');
  }

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

    for (int 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[0] as String;
        final isReachable = msg[1] as bool;

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

        if (progress == '1.00') {
          if (debugLogging) {
            print('Scan finished');
          }
          _isScanInProgress = false;
          _controller.close();
        }

        if (isReachable) {
          _controller.add(
            HostModel(
              ip: hostToPing,
              isReachable: isReachable,
            ),
          );
        }
      });
    }
  }

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

    _isScanInProgress = false;
    _controller.close();
  }

  _controller = StreamController<HostModel>(
    onCancel: stopScan,
    onListen: startScan,
    onPause: stopScan,
    onResume: startScan,
  );

  return _controller.stream;
}