zapretgui/tests/test_dns_runtime.py
loop-uh 28292aba7c
All checks were successful
Zapret source guards / validate (push) Successful in 6s
Windows-сборка / Dev Dev 21.1.6.36 опубликован
DNS переписан на чистом WinAPI: только настоящие адаптеры, виртуалки и VPN не трогаются; добавлены AstraCat и GeoHide
- dns/winapi.py: GetAdaptersAddresses без GAA_FLAG_INCLUDE_ALL_INTERFACES
  (из-за него на странице были WFP/QoS/Npcap-фильтры, отладчик ядра и т.п.),
  признаки «аппаратный / фильтр / разъём» из GetIfEntry2, адаптер интернета
  через GetBestInterfaceEx, DNS читается GetInterfaceDnsSettings вместо
  реестра, пишется SetInterfaceDnsSettings (DoH на Windows 11), кэш —
  DnsFlushResolverCache.
- dns/adapters.py: показываются настоящие сетевые карты (в том числе внутри
  виртуалки) и адаптер, через который идёт интернет (Hyper-V vEthernet);
  внутренние сети Hyper-V/VMware/VirtualBox/WSL, VPN и Wi-Fi Direct не
  показываются и не меняются.
- Адаптер опознаётся по GUID; запись только адаптерам со страницы — Windows
  молча заводит ветку реестра под любой GUID.
- Страница: таблетки со значком Ethernet/Wi-Fi и пометками «интернет» /
  «не подключён», по умолчанию отмечены только подключённые, отметки
  сохраняются после применения; ошибки Windows видны во всплывашке.
- Шаблоны DoH берутся из списка серверов (один источник), IPv6 тоже с DoH.
- Новые DNS из профилей hosts: AstraCat и GeoHide (проверены с Windows).
- Удалены мёртвые dns_core, dns_force, dns_worker, «DNS при запуске»,
  ключи force_dns_enabled и dns_crash_count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:43:52 +03:00

153 lines
5.8 KiB
Python

from __future__ import annotations
import unittest
from unittest.mock import patch
from dns import runtime, winapi
from dns.adapters import DnsAdapter
from dns.address_migration import migrate_outdated_dns_addresses
from dns.dns_providers import DNS_PROVIDERS, doh_templates
from dns.state import DnsState
ETH = "{00000000-0000-0000-0000-000000000009}"
WIFI = "{00000000-0000-0000-0000-000000000012}"
class _FakeWinApi:
def __init__(self, *, doh: bool = False, fail: set[str] = frozenset()) -> None:
self.doh = doh
self.fail = set(fail)
self.writes: list[tuple[str, tuple[str, ...], bool, object]] = []
self.flushes = 0
def write_dns(self, guid, servers, *, ipv6, doh_templates=None):
if guid in self.fail:
raise winapi.DnsWinApiError("SetInterfaceDnsSettings: ошибка Windows 5 — Отказано в доступе.")
self.writes.append((guid, tuple(servers), ipv6, doh_templates))
def flush_resolver_cache(self):
self.flushes += 1
return True
def is_doh_supported(self):
return self.doh
def patches(self):
return [
patch.object(winapi, name, getattr(self, name))
for name in ("write_dns", "flush_resolver_cache", "is_doh_supported")
]
KNOWN = DnsState(
adapters=(
DnsAdapter(ETH, "Ethernet", "", "ethernet", True, True),
DnsAdapter(WIFI, "Беспроводная сеть", "", "wifi", True, False),
)
)
class DnsRuntimeTests(unittest.TestCase):
def _run(self, fake: _FakeWinApi, func, *args):
patchers = [*fake.patches(), patch.object(runtime, "load_state", return_value=KNOWN)]
for item in patchers:
item.start()
self.addCleanup(item.stop)
return func(*args)
def test_apply_writes_both_families_then_flushes_cache(self) -> None:
fake = _FakeWinApi()
result = self._run(fake, runtime.apply_dns, [ETH, WIFI, ETH], ["1.1.1.1", "1.0.0.1"], [])
self.assertEqual(
fake.writes,
[
(ETH, ("1.1.1.1", "1.0.0.1"), False, None),
(ETH, (), True, None),
(WIFI, ("1.1.1.1", "1.0.0.1"), False, None),
(WIFI, (), True, None),
],
)
self.assertEqual(fake.flushes, 1)
self.assertTrue(result.success)
self.assertEqual((result.affected_count, result.total_count), (2, 2))
def test_windows_11_gets_doh_templates_for_known_servers(self) -> None:
fake = _FakeWinApi(doh=True)
self._run(fake, runtime.reset_to_auto, [ETH])
self.assertEqual([write[1] for write in fake.writes], [(), ()])
self.assertEqual(fake.writes[0][3], doh_templates())
def test_failed_adapter_is_reported_by_name(self) -> None:
fake = _FakeWinApi(fail={WIFI})
result = self._run(fake, runtime.apply_dns, [ETH, WIFI], ["9.9.9.9"], ["2620:fe::fe"])
self.assertFalse(result.success)
self.assertEqual((result.affected_count, result.total_count), (1, 2))
self.assertIn("«Беспроводная сеть»: SetInterfaceDnsSettings: ошибка Windows 5", result.message)
def test_unknown_adapter_is_never_written(self) -> None:
# Windows молча принимает любой GUID и заводит под него ветку реестра.
fake = _FakeWinApi()
ghost = "{00000000-0000-0000-0000-000000000000}"
result = self._run(fake, runtime.apply_dns, [ghost], ["1.1.1.1"], [])
self.assertEqual(fake.writes, [])
self.assertEqual(fake.flushes, 0)
self.assertFalse(result.success)
self.assertIn("не найден", result.message)
def test_warmed_state_is_given_out_once(self) -> None:
state = DnsState()
with patch.object(runtime, "load_state", return_value=state):
runtime.warm_state()
self.assertIs(runtime.consume_warmed_state(), state)
self.assertIsNone(runtime.consume_warmed_state())
def test_migration_replaces_only_outdated_static_addresses(self) -> None:
fake = _FakeWinApi()
adapters = (
DnsAdapter(ETH, "Ethernet", "", "ethernet", True, True, static_ipv4=("84.21.189.133", "64.188.98.242"), static_ipv6=("2a12:bec4:1460:d5::2",)),
DnsAdapter(WIFI, "Wi-Fi", "", "wifi", True, False, static_ipv4=("1.1.1.1",)),
)
with patch.object(runtime, "adapters_with_static_dns", return_value=adapters):
changes = self._run(fake, migrate_outdated_dns_addresses)
self.assertEqual(
fake.writes,
[
(ETH, ("95.216.204.218", "80.253.249.40"), False, None),
(ETH, ("2a01:4f9:c014:6dac::1",), True, None),
],
)
self.assertEqual(len(changes), 2)
self.assertEqual(fake.flushes, 1)
class DnsProviderCatalogTests(unittest.TestCase):
def test_every_provider_with_doh_has_templates_for_all_addresses(self) -> None:
templates = doh_templates()
for group in DNS_PROVIDERS.values():
for name, data in group.items():
for address in (*data["ipv4"], *data["ipv6"]):
with self.subTest(provider=name, address=address):
self.assertEqual(templates[address], data["doh"])
def test_hosts_dns_services_are_available_as_dns_servers(self) -> None:
ai = DNS_PROVIDERS["Для ИИ"]
self.assertEqual(ai["Xbox DNS"]["ipv4"], ["111.88.96.54", "111.88.96.55"])
self.assertEqual(ai["AstraCat"]["ipv4"], ["135.106.217.200", "135.106.197.22"])
self.assertEqual(ai["GeoHide"]["ipv4"], ["193.233.112.67", "193.233.112.68"])
self.assertEqual(ai["dns.malw.link"]["ipv4"], ["95.216.204.218", "80.253.249.40"])
if __name__ == "__main__":
unittest.main()