ip_geo: russian dual-stack mismatch note alongside english

This commit is contained in:
Andrew Pimenov 2026-08-09 11:27:20 +03:00
parent ce97958cc9
commit 168cd14f51
4 changed files with 21 additions and 9 deletions

View file

@ -136,11 +136,13 @@ async def _identity(client: httpx.AsyncClient, settings: Settings, options: Opti
v6 = v6_merged if v6_merged.ip else None v6 = v6_merged if v6_merged.ip else None
except (httpx.HTTPError, OSError): except (httpx.HTTPError, OSError):
v6 = None v6 = None
dual_stack = dual_stack_mismatch(merged, v6)
return { return {
"egress_v4": merged, "egress_v4": merged,
"egress_v6": v6, "egress_v6": v6,
"cf_trace": cf, "cf_trace": cf,
"dual_stack_note": dual_stack_mismatch(merged, v6), "dual_stack_note": dual_stack[0] if dual_stack else None,
"dual_stack_note_ru": dual_stack[1] if dual_stack else None,
"_flags": flags, "_flags": flags,
} }

View file

@ -315,8 +315,9 @@ def _connection(report: dict, lang: str = "en") -> list[str]:
[_t(lang, "Cloudflare colo", "Дата-центр Cloudflare"), cf.get("colo") or "—"], [_t(lang, "Cloudflare colo", "Дата-центр Cloudflare"), cf.get("colo") or "—"],
] ]
lines = [title, ""] + _table([_t(lang, "Field", "Поле"), _t(lang, "Value", "Значение")], rows, lang) + [""] lines = [title, ""] + _table([_t(lang, "Field", "Поле"), _t(lang, "Value", "Значение")], rows, lang) + [""]
if geo_bundle.get("dual_stack_note"): note = geo_bundle.get("dual_stack_note_ru" if lang == "ru" else "dual_stack_note")
lines += [f"> {geo_bundle['dual_stack_note']}", ""] if note:
lines += [f"> {note}", ""]
return lines return lines

View file

@ -194,15 +194,20 @@ def merge_geo(candidates: list[tuple[str, IpGeo]]) -> IpGeo:
return merged return merged
def dual_stack_mismatch(v4: IpGeo | None, v6: IpGeo | None) -> str | None: def dual_stack_mismatch(v4: IpGeo | None, v6: IpGeo | None) -> tuple[str, str] | None:
if v4 is None or v6 is None or not v4.asn or not v6.asn: if v4 is None or v6 is None or not v4.asn or not v6.asn:
return None return None
if v4.asn == v6.asn and (v4.country_code or "") == (v6.country_code or ""): if v4.asn == v6.asn and (v4.country_code or "") == (v6.country_code or ""):
return None return None
return ( en = (
f"IPv4 egress {v4.ip} is {v4.asn} ({v4.country_code}) but IPv6 egress {v6.ip} " f"IPv4 egress {v4.ip} is {v4.asn} ({v4.country_code}) but IPv6 egress {v6.ip} "
f"is {v6.asn} ({v6.country_code}); the two stacks leave through different networks." f"is {v6.asn} ({v6.country_code}); the two stacks leave through different networks."
) )
ru = (
f"Исходящий IPv4 {v4.ip} — это {v4.asn} ({v4.country_code}), а исходящий IPv6 {v6.ip} — "
f"{v6.asn} ({v6.country_code}); два стека выходят через разные сети."
)
return en, ru
import asyncio import asyncio

View file

@ -183,10 +183,14 @@ def test_merge_ignores_a_provider_that_returned_nothing():
def test_dual_stack_mismatch_is_reported_not_resolved(): def test_dual_stack_mismatch_is_reported_not_resolved():
v4 = IpGeo(ip="203.0.113.44", asn="AS64500", country_code="NL") v4 = IpGeo(ip="203.0.113.44", asn="AS64500", country_code="NL")
v6 = IpGeo(ip="2001:db8::1", asn="AS64777", country_code="DE") v6 = IpGeo(ip="2001:db8::1", asn="AS64777", country_code="DE")
note = dual_stack_mismatch(v4, v6) pair = dual_stack_mismatch(v4, v6)
assert note is not None assert pair is not None
assert "AS64500" in note en, ru = pair
assert "AS64777" in note assert "AS64500" in en
assert "AS64777" in en
assert "AS64500" in ru
assert "AS64777" in ru
assert any(ch in ru for ch in "абвгдеёжзийклмнопрстуфхцчшщъыьэюя")
def test_dual_stack_agreement_produces_no_note(): def test_dual_stack_agreement_produces_no_note():