zapretgui/scripts/update_subnet_catalog.py
loop-uh c403093fe6
All checks were successful
Zapret source guards / validate (push) Successful in 9s
BlockCheck: «белые списки» подтверждаются или снимаются проверкой подсетей
Признак по контрольным сайтам (российские открываются, зарубежные нет)
держался на трёх-четырёх адресах — его мог дать и провайдер, закрывший
именно их. Теперь при этом признаке BlockCheck спрашивает около 35 сетей:
- diagnostics/subnet_catalog.py: 20 российских (банки, операторы,
  госсервисы) и 15 зарубежных (хостинги и CDN, их собственные адреса),
  по 1–3 адреса из разных /24; страна сети сверена по Team Cymru.
  Пересобирается scripts/update_subnet_catalog.py. Подход —
  hyperion-cs/dpi-checkers (Apache-2.0), состав свой.
- diagnostics/subnet_whitelist.py: TCP 443, ответ или отказ — пакет
  дошёл; закрытые спрашиваются ещё раз; через общий предел прогона.
  Подтверждено: российских ≥60 %, зарубежных ≤10 %; снято: зарубежных
  ≥30 % (так выглядит обычная линия, где РКН закрыл отдельные хостинги).
- problems.whitelist_suspected — общий признак; подтверждение даёт
  твёрдую строку с числами, снятие убирает вывод «белые списки».
  Паспорт KIND_NETWORK дополнен.

Вживую с этой линии: российских 20 из 20, зарубежных 15 из 15 — снято.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:29:07 +03:00

138 lines
6.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Пересобирает diagnostics/subnet_catalog.py: адреса российских и зарубежных сетей для проверки «белого списка».
Запуск из public_zapretgui на машине с обычным интернетом (не в мобильной сети):
PYTHONPATH=src python3 scripts/update_subnet_catalog.py > src/diagnostics/subnet_catalog.py
Адреса узнаются шифрованным DNS (DoH к 1.1.1.1 и 8.8.8.8), в каталог попадают
только те, что сейчас отвечают на TCP 443, — по два-три из разных /24 на
сервис. Подход (российские сети против зарубежных хостингов) взят из
hyperion-cs/dpi-checkers (Apache-2.0); состав сервисов свой.
"""
from __future__ import annotations
import ipaddress
import socket
import sys
from utils.dns_reference import reference_ask
from utils.dns_wire import TYPE_A, query_doh
from utils.ip_owner import lookup_ip_owner
from utils.socket_cancel import SocketCancel
# (подпись, «ru» или «foreign», имена, по которым узнаются адреса сети).
SERVICES: tuple[tuple[str, str, tuple[str, ...]], ...] = (
("Яндекс", "ru", ("ya.ru", "yandex.ru", "dzen.ru")),
("VK", "ru", ("vk.com", "vk.ru")),
("Mail.ru", "ru", ("mail.ru", "ok.ru")),
("Max", "ru", ("max.ru", "web.max.ru")),
("Госуслуги", "ru", ("gosuslugi.ru", "esia.gosuslugi.ru")),
("Сбер", "ru", ("sberbank.ru", "online.sberbank.ru")),
("Т-Банк", "ru", ("tbank.ru", "www.tbank.ru")),
("ВТБ", "ru", ("vtb.ru", "online.vtb.ru")),
("Альфа-Банк", "ru", ("alfabank.ru", "web.alfabank.ru")),
("Ozon", "ru", ("ozon.ru", "www.ozon.ru")),
("Wildberries", "ru", ("wildberries.ru", "www.wildberries.ru")),
("Авито", "ru", ("avito.ru", "www.avito.ru")),
("МТС", "ru", ("mts.ru", "www.mts.ru")),
("МегаФон", "ru", ("megafon.ru", "moscow.megafon.ru")),
("Билайн", "ru", ("beeline.ru", "moskva.beeline.ru")),
("Т2", "ru", ("t2.ru", "msk.t2.ru")),
("Ростелеком", "ru", ("rt.ru", "moscow.rt.ru")),
("ФНС", "ru", ("nalog.gov.ru", "lkfl2.nalog.ru")),
("Мос.ру", "ru", ("mos.ru", "www.mos.ru")),
("Rutube", "ru", ("rutube.ru", "www.rutube.ru")),
("Hetzner", "foreign", ("speed.hetzner.de", "ash-speed.hetzner.com", "hel1-speed.hetzner.com")),
("DigitalOcean", "foreign", ("speedtest-ams3.digitalocean.com", "speedtest-fra1.digitalocean.com", "speedtest-nyc1.digitalocean.com")),
("OVH", "foreign", ("proof.ovh.net", "ovhcloud.com", "www.ovh.com")),
("Vultr", "foreign", ("ams-nl-ping.vultr.com", "fra-de-ping.vultr.com", "par-fr-ping.vultr.com")),
("Linode", "foreign", ("speedtest.frankfurt.linode.com", "speedtest.london.linode.com", "speedtest.newark.linode.com")),
("Leaseweb", "foreign", ("speedtest.ams1.nl.leaseweb.net", "speedtest.fra1.de.leaseweb.net", "speedtest.wdc2.us.leaseweb.net")),
("Cloudflare", "foreign", ("cloudflare.com", "one.one.one.one", "dash.cloudflare.com")),
("Amazon", "foreign", ("ec2.eu-central-1.amazonaws.com", "s3.eu-central-1.amazonaws.com", "dynamodb.us-east-1.amazonaws.com")),
("Google", "foreign", ("google.com", "www.google.com", "dns.google")),
("GitHub", "foreign", ("github.com", "api.github.com")),
("Microsoft", "foreign", ("login.microsoftonline.com", "outlook.office.com", "microsoft.com")),
("Fastly", "foreign", ("fastly.com", "www.fastly.com", "pypi.org")),
("Wikimedia", "foreign", ("wikipedia.org", "en.wikipedia.org", "upload.wikimedia.org")),
("Apple", "foreign", ("gateway.icloud.com", "idmsa.apple.com", "apple.com")),
("Oracle", "foreign", ("oracle.com", "www.oracle.com")),
("Stack Overflow", "foreign", ("stackoverflow.com", "stackexchange.com")),
)
PER_SERVICE = 3
TIMEOUT_S = 3.0
def _addresses(name: str) -> tuple[str, ...]:
found: list[str] = []
for server in ("1.1.1.1", "8.8.8.8"):
for ip in query_doh(server, name, TYPE_A, timeout_s=5.0).values(TYPE_A):
if ip not in found and ipaddress.ip_address(ip).is_global:
found.append(ip)
return tuple(found)
def _answers(ip: str) -> bool:
try:
with socket.create_connection((ip, 443), timeout=TIMEOUT_S):
return True
except OSError:
return False
def _country(ip: str) -> str:
owner = lookup_ip_owner(ip, reference_ask(SocketCancel()))
return owner.country.upper() if owner is not None else ""
def _pick(region: str, names: tuple[str, ...], taken: set[str]) -> tuple[str, ...]:
"""До трёх отвечающих адресов, по одному на /24 и без чужих /24.
Страна сети по Team Cymru должна совпадать с областью группы: зарубежный
сайт, который отдаётся с узла CDN внутри России, проверке не годится.
"""
picked: list[str] = []
for name in names:
for ip in _addresses(name):
net = str(ipaddress.ip_network(f"{ip}/24", strict=False))
if net in taken or not _answers(ip):
continue
if (_country(ip) == "RU") != (region == "ru"):
continue
taken.add(net)
picked.append(ip)
if len(picked) >= PER_SERVICE:
return tuple(picked)
return tuple(picked)
def main() -> int:
# По очереди: одна /24 не должна попасть в две группы.
taken: set[str] = set()
picked = [_pick(region, names, taken) for _label, region, names in SERVICES]
print('"""Адреса российских и зарубежных сетей для проверки «белого списка» (diagnostics.subnet_whitelist).')
print()
print("Собран скриптом scripts/update_subnet_catalog.py: каждый адрес отвечал на TCP 443")
print("с обычной линии. Подход взят из hyperion-cs/dpi-checkers (Apache-2.0), состав свой.")
print('"""')
print()
print("from __future__ import annotations")
print()
print("REGION_RU = \"ru\"")
print("REGION_FOREIGN = \"foreign\"")
print()
print("# (подпись сети, область, адреса из разных /24).")
print("SUBNET_GROUPS: tuple[tuple[str, str, tuple[str, ...]], ...] = (")
for (label, region, _names), ips in zip(SERVICES, picked):
if not ips:
print(f"Нет отвечающих адресов: {label}", file=sys.stderr)
continue
constant = "REGION_RU" if region == "ru" else "REGION_FOREIGN"
print(f" ({label!r}, {constant}, {ips!r}),")
print(")")
return 0
if __name__ == "__main__":
raise SystemExit(main())