Show total IPs, ETA, and live masscan progress ticker

This commit is contained in:
n0mad1k
2026-04-30 14:58:38 -04:00
parent 62906d9676
commit 2e7e7f0cfb
2 changed files with 69 additions and 4 deletions
+32 -2
View File
@@ -4,6 +4,7 @@ geo-scout — geo-targeted network scanner
masscan for port discovery, nmap for fingerprinting, probe engine for matching masscan for port discovery, nmap for fingerprinting, probe engine for matching
""" """
import ipaddress
import json import json
import os import os
import sys import sys
@@ -90,6 +91,33 @@ def collect_all_ports(targets: list[dict]) -> list[int]:
return sorted(ports) return sorted(ports)
def count_ips(cidrs: list[str]) -> int:
total = 0
for c in cidrs:
try:
total += ipaddress.ip_network(c, strict=False).num_addresses
except ValueError:
pass
return total
def fmt_ip_count(n: int) -> str:
if n >= 1_000_000:
return f"{n/1_000_000:.1f}M"
if n >= 1_000:
return f"{n/1_000:.0f}K"
return str(n)
def fmt_eta(total_ips: int, n_ports: int, rate: int) -> str:
if rate <= 0:
return "?"
secs = int(total_ips * n_ports / rate)
m, s = divmod(secs, 60)
h, m = divmod(m, 60)
return f"{h}h {m:02d}m" if h else f"{m}m {s:02d}s"
# ── Probe worker ────────────────────────────────────────────────────────────── # ── Probe worker ──────────────────────────────────────────────────────────────
def probe_host(ip: str, port: int, targets: list[dict]) -> list[dict]: def probe_host(ip: str, port: int, targets: list[dict]) -> list[dict]:
matches = [] matches = []
@@ -183,8 +211,10 @@ def cmd_run(args):
if max_hosts: if max_hosts:
cidrs = cidrs[:max_hosts // 256 + 1] cidrs = cidrs[:max_hosts // 256 + 1]
print(f"\n{C}[{idx}/{n_countries}]{RST} {BOLD}{cc}{RST}{len(cidrs)} CIDR blocks") total_ips = count_ips(cidrs)
info(f"{cc}: running masscan at {rate} pps (Ctrl-C once = skip country, twice = abort all)...") eta = fmt_eta(total_ips, len(all_ports), rate)
print(f"\n{C}[{idx}/{n_countries}]{RST} {BOLD}{cc}{RST}{len(cidrs)} CIDR blocks {W}({fmt_ip_count(total_ips)} IPs ~{eta} at {rate} pps){RST}")
info(f"{cc}: running masscan (Ctrl-C once = skip country, twice = abort all)...")
try: try:
open_ports = masscan_sweep(cidrs, all_ports, rate, tmp) open_ports = masscan_sweep(cidrs, all_ports, rate, tmp)
+37 -2
View File
@@ -1,7 +1,8 @@
import json import json
import shutil import shutil
import subprocess import subprocess
import tempfile import threading
import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
@@ -14,6 +15,12 @@ def check_deps() -> list[str]:
return missing return missing
def _fmt_elapsed(seconds: int) -> str:
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
return f"{h}h{m:02d}m{s:02d}s" if h else f"{m}m{s:02d}s"
def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Path) -> list[dict]: def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Path) -> list[dict]:
if not cidrs or not ports: if not cidrs or not ports:
return [] return []
@@ -32,8 +39,26 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
"--wait", "3", "--wait", "3",
] ]
stop = threading.Event()
start = time.time()
def _progress():
while not stop.is_set():
found = 0
if out_file.exists():
try:
found = out_file.read_text(errors="replace").count('"ip"')
except Exception:
pass
elapsed = _fmt_elapsed(int(time.time() - start))
print(f"\r [{elapsed}] masscan running — {found} hosts found so far ", end="", flush=True)
stop.wait(timeout=4)
t = threading.Thread(target=_progress, daemon=True)
t.start()
try: try:
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL) proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
proc.wait(timeout=3600) proc.wait(timeout=3600)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc.kill() proc.kill()
@@ -41,10 +66,20 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
except KeyboardInterrupt: except KeyboardInterrupt:
proc.kill() proc.kill()
proc.wait() proc.wait()
stop.set()
t.join(timeout=2)
print()
raise raise
except FileNotFoundError: except FileNotFoundError:
stop.set()
t.join(timeout=2)
print()
raise RuntimeError("masscan not found — install it first") raise RuntimeError("masscan not found — install it first")
stop.set()
t.join(timeout=2)
print()
if not out_file.exists(): if not out_file.exists():
return [] return []