Show total IPs, ETA, and live masscan progress ticker
This commit is contained in:
+32
-2
@@ -4,6 +4,7 @@ geo-scout — geo-targeted network scanner
|
||||
masscan for port discovery, nmap for fingerprinting, probe engine for matching
|
||||
"""
|
||||
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -90,6 +91,33 @@ def collect_all_ports(targets: list[dict]) -> list[int]:
|
||||
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 ──────────────────────────────────────────────────────────────
|
||||
def probe_host(ip: str, port: int, targets: list[dict]) -> list[dict]:
|
||||
matches = []
|
||||
@@ -183,8 +211,10 @@ def cmd_run(args):
|
||||
if max_hosts:
|
||||
cidrs = cidrs[:max_hosts // 256 + 1]
|
||||
|
||||
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 once = skip country, twice = abort all)...")
|
||||
total_ips = count_ips(cidrs)
|
||||
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:
|
||||
open_ports = masscan_sweep(cidrs, all_ports, rate, tmp)
|
||||
|
||||
+37
-2
@@ -1,7 +1,8 @@
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
@@ -14,6 +15,12 @@ def check_deps() -> list[str]:
|
||||
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]:
|
||||
if not cidrs or not ports:
|
||||
return []
|
||||
@@ -32,8 +39,26 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
|
||||
"--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:
|
||||
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
proc.wait(timeout=3600)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
@@ -41,10 +66,20 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
|
||||
except KeyboardInterrupt:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
stop.set()
|
||||
t.join(timeout=2)
|
||||
print()
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
stop.set()
|
||||
t.join(timeout=2)
|
||||
print()
|
||||
raise RuntimeError("masscan not found — install it first")
|
||||
|
||||
stop.set()
|
||||
t.join(timeout=2)
|
||||
print()
|
||||
|
||||
if not out_file.exists():
|
||||
return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user