v2: tabs, chain builder, obfuscation modes, hop slider, Defender exclusion, firewall kept
Made-with: Cursor
This commit is contained in:
93
proxy_chain_manager/gost_util.py
Normal file
93
proxy_chain_manager/gost_util.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from .paths import gost_exe_path
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
GOST_RELEASE_ZIP = (
|
||||
"https://github.com/go-gost/gost/releases/download/v3.2.6/"
|
||||
"gost_3.2.6_windows_amd64.zip"
|
||||
)
|
||||
|
||||
|
||||
def _add_defender_exclusion(path: Path) -> None:
|
||||
"""Add Windows Defender exclusion so GOST is not quarantined."""
|
||||
try:
|
||||
subprocess.run(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command",
|
||||
f"Add-MpPreference -ExclusionPath '{path.parent}' -ExclusionProcess 'gost.exe' -ErrorAction SilentlyContinue"],
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||
)
|
||||
log.info("Defender exclusion added for %s", path.parent)
|
||||
except Exception:
|
||||
pass # non-fatal
|
||||
|
||||
|
||||
def ensure_gost(target: Path | None = None) -> Path:
|
||||
exe = target or gost_exe_path()
|
||||
if exe.is_file() and exe.stat().st_size > 10_000:
|
||||
return exe
|
||||
exe.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Add exclusion BEFORE downloading so Defender doesn't nuke it on write
|
||||
_add_defender_exclusion(exe)
|
||||
log.info("Downloading GOST %s", GOST_RELEASE_ZIP)
|
||||
with httpx.Client(timeout=120.0, follow_redirects=True) as c:
|
||||
r = c.get(GOST_RELEASE_ZIP)
|
||||
r.raise_for_status()
|
||||
data = r.content
|
||||
if len(data) < 64 or data[:2] != b"PK":
|
||||
raise RuntimeError("Downloaded GOST zip looks invalid (not a zip).")
|
||||
with zipfile.ZipFile(io.BytesIO(data), "r") as z:
|
||||
names = [n for n in z.namelist() if n.lower().endswith("gost.exe")]
|
||||
if not names:
|
||||
raise RuntimeError("gost.exe not found in release zip")
|
||||
with z.open(names[0]) as src, open(exe, "wb") as dst:
|
||||
shutil.copyfileobj(src, dst)
|
||||
# Add exclusion again after write in case Defender scanned during extraction
|
||||
_add_defender_exclusion(exe)
|
||||
log.info("GOST installed at %s", exe)
|
||||
return exe
|
||||
|
||||
|
||||
def build_gost_cmd(gost: Path, listen_http: str, forwards: list[str]) -> list[str]:
|
||||
args = [str(gost), "-L", f"http://{listen_http}"]
|
||||
for f in forwards:
|
||||
args.extend(["-F", f])
|
||||
return args
|
||||
|
||||
|
||||
def popen_no_window(args: list[str]) -> subprocess.Popen:
|
||||
cr = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
||||
return subprocess.Popen(
|
||||
args,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=cr,
|
||||
)
|
||||
|
||||
|
||||
def terminate_process(proc: subprocess.Popen | None) -> None:
|
||||
if proc is None:
|
||||
return
|
||||
if proc.poll() is not None:
|
||||
return
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=5)
|
||||
except Exception:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user