mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-23 09:20:51 +02:00
Ship ShadowBroker v0.9.83 with live Infonet gate messaging and DM protocols.
Gate hashchain replication, Tor/SOCKS transport hardening, terminal session teardown, v0.9.83 UI/changelog, and release digest pins for seamless updater verification.
This commit is contained in:
@@ -33,6 +33,52 @@ TOR_INSTALL_DIR = TOR_DIR / "tor_bin"
|
||||
_STARTUP_TIMEOUT_S = 90
|
||||
_POLL_INTERVAL_S = 1.0
|
||||
|
||||
|
||||
def _arti_socks_port() -> int:
|
||||
from services.config import get_settings
|
||||
|
||||
return int(get_settings().MESH_ARTI_SOCKS_PORT or 9050)
|
||||
|
||||
|
||||
def _torrc_socks_line(socks_port: int) -> str:
|
||||
return f"SocksPort {socks_port}\n"
|
||||
|
||||
|
||||
def _torrc_has_socks_port(socks_port: int) -> bool:
|
||||
if not TORRC_PATH.exists():
|
||||
return False
|
||||
return _torrc_socks_line(socks_port) in TORRC_PATH.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _local_socks_listening(socks_port: int) -> bool:
|
||||
return _local_socks_handshake_ready(socks_port, timeout=0.75)
|
||||
|
||||
|
||||
def _local_socks_handshake_ready(socks_port: int, *, timeout: float = 5.0) -> bool:
|
||||
import socket
|
||||
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", socks_port), timeout=timeout) as sock:
|
||||
sock.settimeout(timeout)
|
||||
sock.sendall(b"\x05\x01\x00")
|
||||
return sock.recv(2) == b"\x05\x00"
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _write_torrc(*, target_port: int, socks_port: int) -> None:
|
||||
TOR_DIR.mkdir(parents=True, exist_ok=True)
|
||||
hidden_service_dir = TOR_DIR / "hidden_service"
|
||||
hidden_service_dir.mkdir(parents=True, exist_ok=True)
|
||||
torrc_content = (
|
||||
f"DataDirectory {TOR_DATA_DIR.as_posix()}\n"
|
||||
f"HiddenServiceDir {hidden_service_dir.as_posix()}\n"
|
||||
f"HiddenServicePort {target_port} 127.0.0.1:{target_port}\n"
|
||||
f"{_torrc_socks_line(socks_port)}"
|
||||
"Log notice stderr\n"
|
||||
)
|
||||
TORRC_PATH.write_text(torrc_content, encoding="utf-8")
|
||||
|
||||
# Windows x86_64 Tor Expert Bundle URLs. Keep a fallback so first-run
|
||||
# onboarding does not break when Tor rotates point releases.
|
||||
_TOR_EXPERT_BUNDLE_URLS = [
|
||||
@@ -357,12 +403,28 @@ class TorHiddenService:
|
||||
def start(self, target_port: int = 8000) -> dict:
|
||||
"""Start Tor hidden service pointing to target_port on localhost."""
|
||||
with self._lock:
|
||||
socks_port = _arti_socks_port()
|
||||
if self._running and self._process and self._process.poll() is None:
|
||||
return {
|
||||
"ok": True,
|
||||
"onion_address": self._onion_address,
|
||||
"detail": "already running",
|
||||
}
|
||||
if _torrc_has_socks_port(socks_port) and _local_socks_handshake_ready(socks_port, timeout=1.5):
|
||||
return {
|
||||
"ok": True,
|
||||
"onion_address": self._onion_address,
|
||||
"detail": "already running",
|
||||
}
|
||||
logger.info(
|
||||
"Tor is running without a ready SOCKS proxy on port %s — restarting",
|
||||
socks_port,
|
||||
)
|
||||
try:
|
||||
self._process.terminate()
|
||||
self._process.wait(timeout=10)
|
||||
except Exception:
|
||||
try:
|
||||
self._process.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self._process = None
|
||||
self._running = False
|
||||
|
||||
self._error = ""
|
||||
tor_bin = _find_tor_binary()
|
||||
@@ -388,20 +450,9 @@ class TorHiddenService:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
from services.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
socks_port_line = ""
|
||||
if not bool(getattr(settings, "MESH_ARTI_ENABLED", False)):
|
||||
socks_port_line = "SocksPort 9050\n"
|
||||
torrc_content = (
|
||||
f"DataDirectory {TOR_DATA_DIR.as_posix()}\n"
|
||||
f"HiddenServiceDir {hidden_service_dir.as_posix()}\n"
|
||||
f"HiddenServicePort {target_port} 127.0.0.1:{target_port}\n"
|
||||
f"{socks_port_line}"
|
||||
"Log notice stderr\n"
|
||||
)
|
||||
TORRC_PATH.write_text(torrc_content, encoding="utf-8")
|
||||
# Mesh "Arti" transport uses Tor's local SOCKS proxy for .onion peers.
|
||||
# Always publish SocksPort — MESH_ARTI_ENABLED only gates callers, not Tor.
|
||||
_write_torrc(target_port=target_port, socks_port=socks_port)
|
||||
|
||||
try:
|
||||
self._process = subprocess.Popen(
|
||||
@@ -434,15 +485,23 @@ class TorHiddenService:
|
||||
hostname = HOSTNAME_PATH.read_text().strip()
|
||||
if hostname.endswith(".onion"):
|
||||
self._onion_address = f"http://{hostname}:8000"
|
||||
logger.info("Tor hidden service ready: %s", self._onion_address)
|
||||
return {
|
||||
"ok": True,
|
||||
"onion_address": self._onion_address,
|
||||
}
|
||||
if _local_socks_handshake_ready(socks_port, timeout=3.0):
|
||||
logger.info(
|
||||
"Tor hidden service ready: %s (SOCKS %s)",
|
||||
self._onion_address,
|
||||
socks_port,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"onion_address": self._onion_address,
|
||||
}
|
||||
|
||||
time.sleep(_POLL_INTERVAL_S)
|
||||
|
||||
self._error = f"Tor did not generate hostname within {_STARTUP_TIMEOUT_S}s"
|
||||
self._error = (
|
||||
f"Tor did not publish a ready hidden service and SOCKS proxy "
|
||||
f"on port {socks_port} within {_STARTUP_TIMEOUT_S}s"
|
||||
)
|
||||
self.stop()
|
||||
return {"ok": False, "detail": self._error}
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ _STATE_CACHE_TS = 0.0
|
||||
_STATE_CACHE_TTL_S = 2.0
|
||||
_ARTI_PROOF_CACHE: dict[str, Any] = {"port": 0, "ok": False, "ts": 0.0}
|
||||
_ARTI_PROOF_CACHE_TTL_S = 30.0
|
||||
_ARTI_STATUS_CACHE: dict[str, Any] = {"port": 0, "ready": False, "ts": 0.0}
|
||||
_ARTI_STATUS_FAIL_TTL_S = 4.0
|
||||
_ARTI_PROBE_LOCK = threading.Lock()
|
||||
_ARTI_SOCKS_FAILURES = 0
|
||||
_ARTI_LAST_TOR_RECOVERY_TS = 0.0
|
||||
_ARTI_TOR_RECOVERY_COOLDOWN_S = 45.0
|
||||
_ARTI_SOCKS_CONNECT_TIMEOUT_S = 5.0
|
||||
_PRIVATE_CLEARNET_FALLBACK_WINDOW_S = 300.0
|
||||
|
||||
BACKEND_DIR = Path(__file__).resolve().parent.parent
|
||||
@@ -70,16 +77,43 @@ _WORMHOLE_ENV_EXPLICIT = {
|
||||
"PRIVACY_CORE_MIN_VERSION",
|
||||
}
|
||||
|
||||
def _check_arti_ready() -> bool:
|
||||
from services.config import get_settings
|
||||
def invalidate_arti_ready_cache() -> None:
|
||||
_ARTI_PROOF_CACHE.update({"port": 0, "ok": False, "ts": 0.0})
|
||||
_ARTI_STATUS_CACHE.update({"port": 0, "ready": False, "ts": 0.0})
|
||||
|
||||
settings = get_settings()
|
||||
if not bool(settings.MESH_ARTI_ENABLED):
|
||||
return False
|
||||
socks_port = int(settings.MESH_ARTI_SOCKS_PORT or 9050)
|
||||
|
||||
def _maybe_recover_tor_socks_transport(socks_port: int) -> None:
|
||||
global _ARTI_SOCKS_FAILURES, _ARTI_LAST_TOR_RECOVERY_TS
|
||||
|
||||
_ARTI_SOCKS_FAILURES += 1
|
||||
if _ARTI_SOCKS_FAILURES < 3:
|
||||
return
|
||||
now = time.time()
|
||||
if (now - _ARTI_LAST_TOR_RECOVERY_TS) < _ARTI_TOR_RECOVERY_COOLDOWN_S:
|
||||
return
|
||||
_ARTI_LAST_TOR_RECOVERY_TS = now
|
||||
_ARTI_SOCKS_FAILURES = 0
|
||||
try:
|
||||
with socket.create_connection((WORMHOLE_HOST, socks_port), timeout=2.0) as sock:
|
||||
# SOCKS5 greeting: version 5, 1 auth method, no-auth.
|
||||
from services.tor_hidden_service import tor_service
|
||||
|
||||
logger.warning(
|
||||
"Tor SOCKS on port %s is wedged — recycling Tor hidden service",
|
||||
socks_port,
|
||||
)
|
||||
tor_service.stop()
|
||||
tor_service.start(target_port=8000)
|
||||
invalidate_arti_ready_cache()
|
||||
except Exception as exc:
|
||||
logger.warning("Tor SOCKS recovery failed: %s", exc)
|
||||
|
||||
|
||||
def _probe_arti_socks_ready(socks_port: int) -> bool:
|
||||
try:
|
||||
with socket.create_connection(
|
||||
(WORMHOLE_HOST, socks_port),
|
||||
timeout=_ARTI_SOCKS_CONNECT_TIMEOUT_S,
|
||||
) as sock:
|
||||
sock.settimeout(_ARTI_SOCKS_CONNECT_TIMEOUT_S)
|
||||
sock.sendall(b"\x05\x01\x00")
|
||||
response = sock.recv(2)
|
||||
if response != b"\x05\x00":
|
||||
@@ -88,6 +122,53 @@ def _check_arti_ready() -> bool:
|
||||
except Exception as exc:
|
||||
logger.warning("Arti SOCKS check failed on port %s: %s", socks_port, exc)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _check_arti_ready(*, force: bool = False) -> bool:
|
||||
from services.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
if not bool(settings.MESH_ARTI_ENABLED):
|
||||
return False
|
||||
socks_port = int(settings.MESH_ARTI_SOCKS_PORT or 9050)
|
||||
now = time.time()
|
||||
if not force:
|
||||
if (
|
||||
int(_ARTI_STATUS_CACHE.get("port", 0) or 0) == socks_port
|
||||
and (now - float(_ARTI_STATUS_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_STATUS_FAIL_TTL_S
|
||||
):
|
||||
return bool(_ARTI_STATUS_CACHE.get("ready"))
|
||||
if (
|
||||
int(_ARTI_PROOF_CACHE.get("port", 0) or 0) == socks_port
|
||||
and bool(_ARTI_PROOF_CACHE.get("ok"))
|
||||
and (now - float(_ARTI_PROOF_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_PROOF_CACHE_TTL_S
|
||||
):
|
||||
return True
|
||||
|
||||
with _ARTI_PROBE_LOCK:
|
||||
now = time.time()
|
||||
if not force:
|
||||
if (
|
||||
int(_ARTI_STATUS_CACHE.get("port", 0) or 0) == socks_port
|
||||
and (now - float(_ARTI_STATUS_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_STATUS_FAIL_TTL_S
|
||||
):
|
||||
return bool(_ARTI_STATUS_CACHE.get("ready"))
|
||||
if (
|
||||
int(_ARTI_PROOF_CACHE.get("port", 0) or 0) == socks_port
|
||||
and bool(_ARTI_PROOF_CACHE.get("ok"))
|
||||
and (now - float(_ARTI_PROOF_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_PROOF_CACHE_TTL_S
|
||||
):
|
||||
return True
|
||||
|
||||
if not _probe_arti_socks_ready(socks_port):
|
||||
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": False, "ts": now})
|
||||
_maybe_recover_tor_socks_transport(socks_port)
|
||||
return False
|
||||
|
||||
global _ARTI_SOCKS_FAILURES
|
||||
_ARTI_SOCKS_FAILURES = 0
|
||||
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": True, "ts": now})
|
||||
|
||||
now = time.time()
|
||||
if (
|
||||
@@ -110,12 +191,13 @@ def _check_arti_ready() -> bool:
|
||||
is_tor = bool(payload.get("IsTor")) or bool(payload.get("is_tor"))
|
||||
if not (response.ok and is_tor):
|
||||
logger.warning(
|
||||
"Arti Tor proof failed (status=%s is_tor=%s) — SOCKS is up, using Arti anyway",
|
||||
"Arti Tor proof failed (status=%s is_tor=%s)",
|
||||
getattr(response, "status_code", "unknown"),
|
||||
payload.get("IsTor", payload.get("is_tor")),
|
||||
)
|
||||
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": True, "ts": now})
|
||||
return True
|
||||
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": False, "ts": now})
|
||||
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": False, "ts": now})
|
||||
return False
|
||||
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": True, "ts": now})
|
||||
return True
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user