* Add umedbazarov/ruh-vpn: VPN/proxy manager for sing-box New community plugin: bar widget, panel, service and control-center shortcut for managing SSH, VLESS, VMess, Shadowsocks and SOCKS5 connections through sing-box, with routing presets, custom rules, system-proxy/TUN modes and a kill switch. The bundled Python backend serves a loopback control API protected by a per-launch bearer token. * Address review: sanitize kill-switch ruleset, scope TUN capability, fix mux error path, disclose DNS - kill switch: only pre-resolved, canonicalized literal IPs enter the nft ruleset; domains are resolved first and anything unparseable is dropped, so subscription-supplied addresses can no longer inject nft syntax - TUN: CAP_NET_ADMIN is granted to a plugin-private copy of sing-box in a 0700 directory instead of the shared system binary; the copy is refreshed (clearing the cap) when the system binary changes, and the legacy grant on the shared binary is removed in the same polkit prompt - fix NameError in the mux startup failure path (undefined mux_name) that hid the log tail and skipped teardown - README: disclose plain-UDP DNS endpoints (8.8.8.8 via tunnel, 223.5.5.5 direct in rules mode) alongside the TUN DoH endpoint --------- Co-authored-by: Umedjon Bazarov <170195993+UmedjonBA@users.noreply.github.com>
45 lines
1.6 KiB
Python
45 lines
1.6 KiB
Python
import json
|
|
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from backend.routing.rules import PRESETS
|
|
from backend.singbox import config_builder
|
|
|
|
ALL = list(PRESETS.keys())
|
|
|
|
|
|
def test_rules_config_includes_presets():
|
|
cfg = config_builder.build_rules_config(active_presets=ALL)
|
|
tags = {rs["tag"] for rs in cfg["route"]["rule_set"]}
|
|
expected = {rs["tag"] for p in PRESETS.values() for rs in p["rule_sets"]}
|
|
assert expected <= tags
|
|
proxy_rules = [r for r in cfg["route"]["rules"] if r.get("outbound") == "proxy" and "rule_set" in r]
|
|
assert len(proxy_rules) == len(ALL)
|
|
|
|
|
|
def test_rules_config_dns_covers_domain_rule_sets():
|
|
cfg = config_builder.build_rules_config(active_presets=ALL)
|
|
dns_rule_sets = [r["rule_set"] for r in cfg["dns"]["rules"] if "rule_set" in r]
|
|
flattened = {t for group in dns_rule_sets for t in group}
|
|
assert "refilter_domains" in flattened
|
|
assert "geosite_noncn" in flattened
|
|
assert "geosite_sanctioned" in flattened
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("sing-box") is None, reason="sing-box not installed")
|
|
@pytest.mark.parametrize("presets", [[], ALL])
|
|
def test_sing_box_accepts_generated_configs(tmp_path, presets):
|
|
for name, cfg in {
|
|
"rules": config_builder.build_rules_config(active_presets=presets),
|
|
"global": config_builder.build_global_config(),
|
|
}.items():
|
|
path = tmp_path / f"{name}.json"
|
|
path.write_text(json.dumps(cfg))
|
|
proc = subprocess.run(
|
|
["sing-box", "check", "-c", str(path)],
|
|
capture_output=True, text=True, timeout=30,
|
|
)
|
|
assert proc.returncode == 0, f"{name}: {proc.stderr}"
|