fix: adopt sing-box 1.14 DNS hijacking
This commit is contained in:
+2
-13
@@ -37,7 +37,7 @@
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
"mtu": 1400,
|
||||
"dns_mode": "disabled",
|
||||
"dns_mode": "hijack",
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
@@ -69,7 +69,6 @@
|
||||
"server_name": "fi.api.withen.pro",
|
||||
"insecure": false
|
||||
},
|
||||
"disable_chrome_parrot": false,
|
||||
"bind_interface": "eth0",
|
||||
"domain_resolver": {
|
||||
"server": "bootstrap-dns",
|
||||
@@ -90,16 +89,6 @@
|
||||
"server": "bootstrap-dns",
|
||||
"strategy": "ipv4_only"
|
||||
},
|
||||
"final": "hy2-out",
|
||||
"rules": [
|
||||
{
|
||||
"network": [
|
||||
"tcp",
|
||||
"udp"
|
||||
],
|
||||
"port": 53,
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
]
|
||||
"final": "hy2-out"
|
||||
}
|
||||
}
|
||||
|
||||
+24
-2
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -25,11 +27,31 @@ class PackagingTests(unittest.TestCase):
|
||||
control = (ROOT / "debian/control").read_text(encoding="utf-8")
|
||||
self.assertIn("sing-box (= 1.14.0)", control)
|
||||
|
||||
def test_in_place_upgrade_is_rejected(self) -> None:
|
||||
def test_cross_release_upgrade_is_rejected_and_same_release_reinstall_allowed(self) -> None:
|
||||
preinst = (ROOT / "debian/preinst").read_text(encoding="utf-8")
|
||||
self.assertIn('[ "$1" = upgrade ]', preinst)
|
||||
self.assertIn('[ "$1" = upgrade ] && [ "${2:-}" != "0.2.0" ]', preinst)
|
||||
self.assertIn("in-place upgrades from another release", preinst)
|
||||
self.assertIn("exit 1", preinst)
|
||||
|
||||
@unittest.skipUnless(os.name == "posix", "Debian maintainer scripts require POSIX sh")
|
||||
def test_preinst_lifecycle_exit_codes(self) -> None:
|
||||
preinst = ROOT / "debian/preinst"
|
||||
cases = (
|
||||
(("install",), 0),
|
||||
(("upgrade", "0.2.0", "0.2.0"), 0),
|
||||
(("upgrade", "0.1.0", "0.2.0"), 1),
|
||||
(("upgrade", "0.2.1", "0.2.0"), 1),
|
||||
)
|
||||
for arguments, expected in cases:
|
||||
with self.subTest(arguments=arguments):
|
||||
result = subprocess.run(
|
||||
["/bin/sh", str(preinst), *arguments],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertEqual(result.returncode, expected, result.stderr)
|
||||
|
||||
def test_postinst_checks_for_legacy_state(self) -> None:
|
||||
postinst = (ROOT / "debian/postinst").read_text(encoding="utf-8")
|
||||
self.assertIn("vpn_egressctl.installcheck", postinst)
|
||||
|
||||
@@ -38,7 +38,8 @@ class RendererTests(unittest.TestCase):
|
||||
self.assertEqual(tun["auto_redirect_input_mark"], "0x2023")
|
||||
self.assertEqual(tun["auto_redirect_output_mark"], "0x2024")
|
||||
self.assertEqual(tun["auto_redirect_reset_mark"], "0x2025")
|
||||
self.assertEqual(tun["dns_mode"], "disabled")
|
||||
self.assertEqual(tun["dns_mode"], "hijack")
|
||||
self.assertNotIn("dns_address", tun)
|
||||
outbound = config["outbounds"][0]
|
||||
self.assertEqual(outbound["server"], "fi.api.withen.pro")
|
||||
self.assertEqual(outbound["password"], "AUTH")
|
||||
@@ -46,12 +47,13 @@ class RendererTests(unittest.TestCase):
|
||||
self.assertEqual(outbound["obfs"]["type"], "gecko")
|
||||
self.assertEqual(outbound["obfs"]["min_packet_size"], 512)
|
||||
self.assertEqual(outbound["obfs"]["max_packet_size"], 1200)
|
||||
self.assertFalse(outbound["disable_chrome_parrot"])
|
||||
self.assertNotIn("disable_chrome_parrot", outbound)
|
||||
self.assertNotIn("bbr_profile", outbound)
|
||||
self.assertEqual(outbound["bind_interface"], "eth0")
|
||||
self.assertEqual([item["tag"] for item in config["outbounds"]], ["hy2-out"])
|
||||
self.assertEqual(config["dns"]["servers"][1]["detour"], "hy2-out")
|
||||
self.assertEqual(config["route"]["final"], "hy2-out")
|
||||
self.assertNotIn("rules", config["route"])
|
||||
self.assertNotIn("185.156.108.141", render_bytes(policy, endpoint).decode())
|
||||
|
||||
def test_salamander_fallback_does_not_render_gecko_fields(self) -> None:
|
||||
|
||||
@@ -119,11 +119,49 @@ class TransactionTests(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
controller, runner, policy = self.make(directory)
|
||||
endpoint = parse_hysteria2_uri(URI_NEW)
|
||||
Path(policy.sing_box.config_path).write_bytes(render_bytes(policy, endpoint))
|
||||
installed = render_bytes(policy, endpoint)
|
||||
Path(policy.sing_box.config_path).write_bytes(installed)
|
||||
self.mark_managed(controller, installed, URI_NEW)
|
||||
self.assertFalse(controller.sync())
|
||||
self.assertFalse(any("restart" in call for call in runner.calls))
|
||||
self.assertFalse(json.loads(controller.state_path.read_text())["changed"])
|
||||
|
||||
def test_identical_unmanaged_config_is_not_adopted(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
controller, runner, policy = self.make(directory)
|
||||
endpoint = parse_hysteria2_uri(URI_NEW)
|
||||
installed = render_bytes(policy, endpoint)
|
||||
target = Path(policy.sing_box.config_path)
|
||||
target.write_bytes(installed)
|
||||
|
||||
with self.assertRaisesRegex(ValidationError, "clean installation"):
|
||||
controller.sync()
|
||||
|
||||
self.assertEqual(target.read_bytes(), installed)
|
||||
self.assertFalse(controller.state_path.exists())
|
||||
self.assertFalse(controller.last_good_path.exists())
|
||||
self.assertFalse(any("restart" in call for call in runner.calls))
|
||||
self.assertEqual(list(target.parent.glob(f".{target.name}.candidate.*")), [])
|
||||
|
||||
def test_import_restores_uri_when_identical_config_is_unmanaged(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
controller, runner, policy = self.make(directory)
|
||||
uri_path = Path(policy.runtime.uri_path)
|
||||
uri_path.write_text(URI_OLD + "\n", encoding="utf-8")
|
||||
endpoint = parse_hysteria2_uri(URI_NEW)
|
||||
installed = render_bytes(policy, endpoint)
|
||||
target = Path(policy.sing_box.config_path)
|
||||
target.write_bytes(installed)
|
||||
|
||||
with self.assertRaisesRegex(ValidationError, "clean installation"):
|
||||
controller.import_uri(URI_NEW)
|
||||
|
||||
self.assertEqual(uri_path.read_text(encoding="utf-8").strip(), URI_OLD)
|
||||
self.assertEqual(target.read_bytes(), installed)
|
||||
self.assertFalse(controller.state_path.exists())
|
||||
self.assertFalse(any("restart" in call for call in runner.calls))
|
||||
self.assertEqual(list(target.parent.glob(f".{target.name}.candidate.*")), [])
|
||||
|
||||
def test_generated_config_rejection_is_non_mutating(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
controller, _, policy = self.make(directory, runner=FakeRunner(check_result=1))
|
||||
@@ -247,7 +285,9 @@ class TransactionTests(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
controller, _, policy = self.make(directory)
|
||||
endpoint = parse_hysteria2_uri(URI_NEW)
|
||||
Path(policy.sing_box.config_path).write_bytes(render_bytes(policy, endpoint))
|
||||
installed = render_bytes(policy, endpoint)
|
||||
Path(policy.sing_box.config_path).write_bytes(installed)
|
||||
self.mark_managed(controller, installed, URI_NEW)
|
||||
controller.sync()
|
||||
Path(policy.runtime.uri_path).write_text(URI_OLD + "\n", encoding="utf-8")
|
||||
status = controller.status()
|
||||
|
||||
Reference in New Issue
Block a user