Add live scan progress and graceful Ctrl-C handling

This commit is contained in:
n0mad1k
2026-04-30 14:49:57 -04:00
parent feb34ada8b
commit 615937b20b
2 changed files with 89 additions and 61 deletions
+33 -10
View File
@@ -147,11 +147,16 @@ def cmd_run(args):
total_hosts = 0
total_matches = 0
workers = args.workers
n_countries = len(countries)
def progress(msg: str, end="\n"):
print(f" {W}{msg}{RST}", end=end, flush=True)
try:
with tempfile.TemporaryDirectory() as tmpdir:
tmp = Path(tmpdir)
for country in countries:
for idx, country in enumerate(countries, 1):
cc = country["code"].upper()
exclude = country.get("exclude_cidrs", [])
cidrs = get_cidrs(cc, cidr_index)
@@ -164,12 +169,19 @@ def cmd_run(args):
if max_hosts:
cidrs = cidrs[:max_hosts // 256 + 1]
info(f"{BOLD}{cc}{RST}: {len(cidrs)} CIDR blocks → masscan...")
print(f"\n{C}[{idx}/{n_countries}]{RST} {BOLD}{cc}{RST} {len(cidrs)} CIDR blocks")
info(f"{cc}: running masscan at {rate} pps (Ctrl-C to skip country)...")
try:
open_ports = masscan_sweep(cidrs, all_ports, rate, tmp)
except KeyboardInterrupt:
print()
warn(f"{cc}: scan interrupted — skipping to next country")
continue
hosts_found = len(set(e["ip"] for e in open_ports))
total_hosts += hosts_found
ok(f"{cc}: {hosts_found} live hosts ({len(open_ports)} open ports)")
ok(f"{cc}: {hosts_found} live hosts, {len(open_ports)} open ports found")
if not open_ports:
continue
@@ -179,25 +191,33 @@ def cmd_run(args):
for entry in open_ports:
ip_ports.setdefault(entry["ip"], []).append(entry["port"])
info(f"{cc}: fingerprinting {hosts_found} hosts with nmap...")
total_nmap = len(ip_ports)
info(f"{cc}: fingerprinting {total_nmap} hosts with nmap...")
nmap_results: dict[str, dict] = {}
nmap_done = 0
with ThreadPoolExecutor(max_workers=min(workers, 10)) as ex:
futs = {ex.submit(nmap_fingerprint, ip, ports, tmp): ip
for ip, ports in ip_ports.items()}
for fut in as_completed(futs):
ip = futs[fut]
nmap_done += 1
progress(f"nmap: {nmap_done}/{total_nmap}", end="\r")
try:
nmap_results[ip] = fut.result()
except Exception:
nmap_results[ip] = {}
print()
info(f"{cc}: probing and matching targets...")
total_probes = len(open_ports)
info(f"{cc}: probing {total_probes} open ports for target matches...")
probe_done = 0
with ThreadPoolExecutor(max_workers=workers) as ex:
futs = []
for entry in open_ports:
futs.append(ex.submit(probe_host, entry["ip"], entry["port"], targets))
futs = [ex.submit(probe_host, entry["ip"], entry["port"], targets)
for entry in open_ports]
for fut in as_completed(futs):
probe_done += 1
progress(f"probe: {probe_done}/{total_probes}", end="\r")
try:
matches = fut.result()
except Exception:
@@ -215,10 +235,13 @@ def cmd_run(args):
db.insert_hit(conn, run_id, hit)
total_matches += 1
ver = f" [{match['version']}]" if match.get("version") else ""
print(f" {G}HIT{RST} {BOLD}{ip}:{port}{RST} {Y}{match['target_name']}{RST}{ver} [{match['confidence']}] {cc}")
print(f"\r {G}HIT{RST} {BOLD}{ip}:{port}{RST} {Y}{match['target_name']}{RST}{ver} [{match['confidence']}] {cc}")
print()
except KeyboardInterrupt:
print()
warn("Scan aborted by user")
db.finish_run(conn, run_id, total_hosts, total_matches)
conn.close()
+8 -3
View File
@@ -29,14 +29,19 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
f"--ports={port_str}",
"-iL", str(cidr_file),
"-oJ", str(out_file),
"--output-format", "json",
"--wait", "3",
]
try:
subprocess.run(cmd, capture_output=True, timeout=3600)
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
proc.wait(timeout=3600)
except subprocess.TimeoutExpired:
pass
proc.kill()
proc.wait()
except KeyboardInterrupt:
proc.kill()
proc.wait()
raise
except FileNotFoundError:
raise RuntimeError("masscan not found — install it first")