Harden resolver and expand traffic runtime controls
This commit is contained in:
@@ -105,6 +105,74 @@ def infer_app_key(cmdline: str) -> str:
|
||||
return canonicalize_app_key("", cmdline)
|
||||
|
||||
|
||||
def browser_harden_enabled() -> bool:
|
||||
raw = str(os.environ.get("SVPN_BROWSER_HARDEN", "1") or "1").strip().lower()
|
||||
return raw not in ("0", "false", "no", "off")
|
||||
|
||||
|
||||
def is_chromium_like_cmd(tokens: list[str]) -> bool:
|
||||
toks = [str(x or "").strip() for x in (tokens or []) if str(x or "").strip()]
|
||||
if not toks:
|
||||
return False
|
||||
exe = os.path.basename(toks[0]).lower()
|
||||
known = {
|
||||
"google-chrome",
|
||||
"google-chrome-stable",
|
||||
"chromium",
|
||||
"chromium-browser",
|
||||
"microsoft-edge",
|
||||
"microsoft-edge-stable",
|
||||
"brave",
|
||||
"brave-browser",
|
||||
"opera",
|
||||
"opera-beta",
|
||||
"opera-developer",
|
||||
"vivaldi",
|
||||
"vivaldi-stable",
|
||||
}
|
||||
if exe in known:
|
||||
return True
|
||||
if any(x in exe for x in ("chrome", "chromium", "edge", "brave", "opera", "vivaldi")):
|
||||
return True
|
||||
if exe == "flatpak":
|
||||
for i, t in enumerate(toks):
|
||||
if t == "run":
|
||||
for cand in toks[i + 1:]:
|
||||
c = cand.strip().lower()
|
||||
if not c or c.startswith("-") or c == "--":
|
||||
continue
|
||||
return any(x in c for x in ("chrome", "chromium", "edge", "brave", "opera", "vivaldi"))
|
||||
break
|
||||
return False
|
||||
|
||||
|
||||
def maybe_harden_browser_cmdline(cmdline: str) -> str:
|
||||
raw = (cmdline or "").strip()
|
||||
if not raw or not browser_harden_enabled():
|
||||
return raw
|
||||
try:
|
||||
toks = shlex.split(raw)
|
||||
except Exception:
|
||||
return raw
|
||||
if not is_chromium_like_cmd(toks):
|
||||
return raw
|
||||
flags = [
|
||||
"--disable-quic",
|
||||
"--force-webrtc-ip-handling-policy=disable_non_proxied_udp",
|
||||
]
|
||||
low = [t.lower() for t in toks]
|
||||
changed = False
|
||||
for fl in flags:
|
||||
fl_low = fl.lower()
|
||||
if any(t == fl_low or t.startswith(fl_low + "=") for t in low):
|
||||
continue
|
||||
toks.append(fl)
|
||||
changed = True
|
||||
if not changed:
|
||||
return raw
|
||||
return " ".join(shlex.quote(t) for t in toks)
|
||||
|
||||
|
||||
def canonicalize_app_key(app_key: str, cmdline: str) -> str:
|
||||
key = (app_key or "").strip()
|
||||
cmd = (cmdline or "").strip()
|
||||
@@ -181,6 +249,19 @@ def systemctl_user(args: list[str], *, timeout: float = 4.0) -> tuple[int, str]:
|
||||
out = ((p.stdout or "") + (p.stderr or "")).strip()
|
||||
return int(p.returncode or 0), out
|
||||
|
||||
def stop_user_unit_best_effort(unit: str) -> tuple[bool, str]:
|
||||
u = (unit or "").strip()
|
||||
if not u:
|
||||
return False, "empty unit"
|
||||
code, out = systemctl_user(["stop", u], timeout=4.0)
|
||||
if code == 0:
|
||||
return True, out
|
||||
code2, out2 = systemctl_user(["kill", u], timeout=4.0)
|
||||
if code2 == 0:
|
||||
return True, out2
|
||||
msg = (out2 or out or f"stop/kill failed for {u}").strip()
|
||||
return False, msg
|
||||
|
||||
|
||||
def cgroup_path_from_pid(pid: int) -> str:
|
||||
p = int(pid or 0)
|
||||
@@ -246,7 +327,13 @@ def run_systemd_unit(cmdline: str, *, unit: str) -> str:
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(f"systemd-run failed: rc={p.returncode}\n{out}".strip())
|
||||
|
||||
cg = effective_cgroup_for_unit(unit, timeout_sec=3.0)
|
||||
try:
|
||||
cg = effective_cgroup_for_unit(unit, timeout_sec=3.0)
|
||||
except Exception as e:
|
||||
stopped, stop_msg = stop_user_unit_best_effort(unit)
|
||||
if stopped:
|
||||
raise RuntimeError(f"{e}\n\nUnit was stopped (fail-closed): {unit}") from e
|
||||
raise RuntimeError(f"{e}\n\nWARNING: failed to stop unit {unit}: {stop_msg}") from e
|
||||
return cg
|
||||
|
||||
|
||||
@@ -307,7 +394,8 @@ def apply_mark(*, target: str, cgroup: str, unit: str, command: str, app_key: st
|
||||
res = api_request("POST", "/api/v1/traffic/appmarks", json_body=payload, timeout=4.0)
|
||||
if not bool(res.get("ok", False)):
|
||||
raise RuntimeError(f"appmark failed: {res.get('message')}")
|
||||
log(f"mark added: target={target} app={app_key} unit={unit} cgroup_id={res.get('cgroup_id')} ttl={res.get('timeout_sec')}")
|
||||
ttl_txt = "persistent" if int(res.get("timeout_sec", 0) or 0) <= 0 else f"{int(res.get('timeout_sec', 0) or 0)}s"
|
||||
log(f"mark added: target={target} app={app_key} unit={unit} cgroup_id={res.get('cgroup_id')} ttl={ttl_txt}")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
@@ -322,27 +410,36 @@ def main(argv: list[str]) -> int:
|
||||
cmd = str(prof.get("command") or "").strip()
|
||||
if not cmd:
|
||||
raise RuntimeError("profile command is empty")
|
||||
run_cmd = maybe_harden_browser_cmdline(cmd)
|
||||
if run_cmd != cmd:
|
||||
log("browser hardening: added anti-leak flags")
|
||||
target = str(prof.get("target") or "vpn").strip().lower()
|
||||
if target not in ("vpn", "direct"):
|
||||
target = "vpn"
|
||||
|
||||
app_key_raw = str(prof.get("app_key") or "").strip()
|
||||
app_key = canonicalize_app_key(app_key_raw, cmd) or canonicalize_app_key("", cmd)
|
||||
app_key = canonicalize_app_key(app_key_raw, run_cmd) or canonicalize_app_key("", run_cmd)
|
||||
ttl = int(prof.get("ttl_sec", 0) or 0)
|
||||
if ttl <= 0:
|
||||
ttl = 24 * 60 * 60
|
||||
if ttl < 0:
|
||||
ttl = 0
|
||||
|
||||
# Try refresh first if already running.
|
||||
if refresh_if_running(target=target, app_key=app_key, command=cmd, ttl_sec=ttl):
|
||||
if refresh_if_running(target=target, app_key=app_key, command=run_cmd, ttl_sec=ttl):
|
||||
if args.json:
|
||||
print(json.dumps({"ok": True, "op": "refresh", "id": pid, "target": target, "app_key": app_key}))
|
||||
return 0
|
||||
|
||||
unit = f"svpn-{target}-{int(time.time())}.service"
|
||||
log(f"launching profile id={pid} target={target} app={app_key} unit={unit}")
|
||||
cg = run_systemd_unit(cmd, unit=unit)
|
||||
cg = run_systemd_unit(run_cmd, unit=unit)
|
||||
log(f"ControlGroup: {cg}")
|
||||
apply_mark(target=target, cgroup=cg, unit=unit, command=cmd, app_key=app_key, ttl_sec=ttl)
|
||||
try:
|
||||
apply_mark(target=target, cgroup=cg, unit=unit, command=run_cmd, app_key=app_key, ttl_sec=ttl)
|
||||
except Exception as e:
|
||||
stopped, stop_msg = stop_user_unit_best_effort(unit)
|
||||
if stopped:
|
||||
raise RuntimeError(f"{e}\n\nUnit was stopped (fail-closed): {unit}") from e
|
||||
raise RuntimeError(f"{e}\n\nWARNING: failed to stop unit {unit}: {stop_msg}") from e
|
||||
if args.json:
|
||||
print(json.dumps({"ok": True, "op": "run", "id": pid, "target": target, "app_key": app_key, "unit": unit}))
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user