From 4c2da2e8316e7ffcdae282e4ab8db3ae3072293a Mon Sep 17 00:00:00 2001 From: Andrew Pimenov Date: Sun, 9 Aug 2026 04:10:44 +0300 Subject: [PATCH] dns_leak: russian note alongside english --- src/netcheck/probes/dns_leak.py | 18 +++++++++++++++++- tests/test_dns_leak.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/netcheck/probes/dns_leak.py b/src/netcheck/probes/dns_leak.py index 21ee2a8..c388293 100644 --- a/src/netcheck/probes/dns_leak.py +++ b/src/netcheck/probes/dns_leak.py @@ -8,6 +8,10 @@ _LEAK_NOTE = ( "This test only sees resolvers configured at the OS level. A browser using DoH " "or DoT bypasses them entirely and is not covered here." ) +_LEAK_NOTE_RU = ( + "Этот тест видит только резолверы, настроенные на уровне ОС. Браузер, использующий DoH " + "или DoT, полностью обходит их и здесь не учитывается." +) def _unquote(text: str) -> str: @@ -66,16 +70,28 @@ def build_adapter_result( def build_dns_leak(results: list[AdapterLeakResult], ecs_leaked: bool) -> DnsLeak: leaking = [r.adapter for r in results if r.matches_egress_asn is False] parts: list[str] = [] + parts_ru: list[str] = [] if leaking: parts.append( f"DNS queries from {', '.join(leaking)} resolve through a network outside the egress ASN." ) + parts_ru.append( + f"DNS-запросы с {', '.join(leaking)} разрешаются через сеть за пределами egress ASN." + ) else: parts.append("No adapter resolves DNS outside the egress ASN.") + parts_ru.append("Ни один адаптер не резолвит DNS за пределами egress ASN.") if ecs_leaked: parts.append("The resolver forwards your EDNS Client Subnet, exposing your network to authoritative servers.") + parts_ru.append("Резолвер пересылает ваш EDNS Client Subnet, раскрывая вашу сеть авторитативным серверам.") parts.append(_LEAK_NOTE) - return DnsLeak(per_adapter=results, ecs_leaked=ecs_leaked, note=" ".join(parts)) + parts_ru.append(_LEAK_NOTE_RU) + return DnsLeak( + per_adapter=results, + ecs_leaked=ecs_leaked, + note=" ".join(parts), + note_ru=" ".join(parts_ru), + ) import asyncio diff --git a/tests/test_dns_leak.py b/tests/test_dns_leak.py index 5754034..43218e9 100644 --- a/tests/test_dns_leak.py +++ b/tests/test_dns_leak.py @@ -93,3 +93,33 @@ def test_dns_leak_note_mentions_ecs_when_the_subnet_leaked(): leak = build_dns_leak([clean], ecs_leaked=True) assert leak.ecs_leaked is True assert "client subnet" in leak.note.lower() + + +_CYRILLIC = set("абвгдеёжзийклмнопрстуфхцчшщъыьэюя") + + +def has_cyrillic(text: str) -> bool: + return any(ch in _CYRILLIC for ch in text.lower()) + + +def test_dns_leak_note_ru_names_the_leaking_adapter(): + leaking = build_adapter_result("Wi-Fi", ["192.168.1.1"], "203.0.113.9", "AS64501", "AS64500") + clean = build_adapter_result("wg0", ["10.7.0.1"], "203.0.113.44", "AS64500", "AS64500") + leak = build_dns_leak([clean, leaking], ecs_leaked=False) + assert leak.note_ru + assert has_cyrillic(leak.note_ru) + assert "Wi-Fi" in leak.note_ru + + +def test_dns_leak_note_ru_is_clean_when_every_adapter_agrees(): + clean = build_adapter_result("wg0", ["10.7.0.1"], "203.0.113.44", "AS64500", "AS64500") + leak = build_dns_leak([clean], ecs_leaked=False) + assert leak.note_ru + assert has_cyrillic(leak.note_ru) + + +def test_dns_leak_note_ru_mentions_ecs_when_the_subnet_leaked(): + clean = build_adapter_result("wg0", ["10.7.0.1"], "203.0.113.44", "AS64500", "AS64500") + leak = build_dns_leak([clean], ecs_leaked=True) + assert leak.note_ru + assert has_cyrillic(leak.note_ru)