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
|
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
@@ -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:
|
try:
|
||||||
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
|
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, 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 []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user