scanNetwork function

Future<Map<String, Map<int, Map<String, dynamic>>>> scanNetwork(
  1. Iterable<String> hosts,
  2. List<int> ports, {
  3. int hostConcurrency = 10,
  4. int portConcurrency = 50,
  5. Duration timeout = const Duration(milliseconds: 300),
  6. Duration bannerTimeout = const Duration(milliseconds: 300),
  7. void onHostResult(
    1. String host,
    2. Map<int, Map<String, dynamic>> result
    )?,
  8. IOSink? jsonlSink,
})

Scan multiple hosts concurrently (host-level concurrency) and scan each host's ports using scanHost. Returns a map host->(port->banner).

Implementation

Future<Map<String, Map<int, Map<String, dynamic>>>> scanNetwork(
  Iterable<String> hosts,
  List<int> ports, {
  int hostConcurrency = 10,
  int portConcurrency = 50,
  Duration timeout = const Duration(milliseconds: 300),
  Duration bannerTimeout = const Duration(milliseconds: 300),
  void Function(String host, Map<int, Map<String, dynamic>> result)?
  onHostResult,
  // If provided, write per-host JSON lines to this sink as results arrive.
  IOSink? jsonlSink,
}) async {
  final results = <String, Map<int, Map<String, dynamic>>>{};
  final hostPool = Pool(hostConcurrency);
  final futures = <Future>[];

  for (final h in hosts) {
    futures.add(
      hostPool.withResource(() async {
        final r = await scanHost(
          h,
          ports,
          portConcurrency: portConcurrency,
          timeout: timeout,
          bannerTimeout: bannerTimeout,
        );
        // Always record results (may be empty) and stream a JSONL line if requested
        results[h] = r;
        // Build per-host JSON object
        final hostObj = <String, dynamic>{
          'host': h,
          'timestamp': DateTime.now().toIso8601String(),
          'open_count': r.length,
        };
        final portsList = <Map<String, dynamic>>[];
        for (final entry in r.entries) {
          final p = entry.key;
          final banner = entry.value['banner'];
          final proto = entry.value['protocol'];
          final service = entry.value['service'] ?? _wellKnownPortService(p);
          final severity = entry.value['severity'];
          portsList.add({
            'port': p,
            'banner': banner,
            'protocol': proto,
            'service': service,
            'severity': severity,
          });
        }
        hostObj['ports'] = portsList;
        // If HTTPS likely present (443 open), try cert inspection
        if (r.keys.contains(443)) {
          try {
            final cert = await inspectTlsCertificate(h, 443);
            if (cert != null) hostObj['tls'] = cert;
          } catch (_) {
            // ignore cert errors
          }
        }

        // Use the separated http probe to gather headers and resolved IP.
        Map<String, String>? headersMap;
        String? cnameTarget;
        try {
          final probe = await http_probe.httpHeadProbe(
            h,
            timeout: Duration(seconds: 5),
          );
          headersMap = probe['headers'] as Map<String, String>?;
          final ip = probe['ip'] as String?;
          if (ip != null) {
            final geo = await geoIpLookup(ip);
            if (geo != null) hostObj['geoip'] = geo;
          }
          try {
            final trace = await traceroute(h);
            if (trace.isNotEmpty) hostObj['traceroute'] = trace;
          } catch (_) {}
        } catch (_) {
          headersMap = null;
        }

        // Fingerprint service and detect WAF/CDN
        final fp = fingerprintService(headers: headersMap, banner: null);
        final waf = detectWafCdns(headersMap, cnameTarget);
        if (fp['name'] != null) hostObj['fingerprint'] = fp;
        if (waf.isNotEmpty) hostObj['waf_cdns'] = waf;
        if (jsonlSink != null) {
          jsonlSink.writeln(JsonEncoder().convert(hostObj));
          await jsonlSink.flush();
        }
        try {
          if (onHostResult != null) onHostResult(h, r);
        } catch (_) {
          // ignore callback errors
        }
      }),
    );
  }

  await Future.wait(futures);
  // If streaming JSONL, produce a final aggregated severity summary line
  if (jsonlSink != null) {
    final summary = <String, dynamic>{
      'timestamp': DateTime.now().toIso8601String(),
      'summary': reporting.severitySummary(results),
    };
    jsonlSink.writeln(JsonEncoder().convert(summary));
    await jsonlSink.flush();
  }

  return results;
}