Skip to main content

Diagnose RKN/TSPU internet blocks layer by layer (DNS, TCP, TLS, HTTP).

Project description

RKN Block Checker

PyPICI Python License: MIT

A small CLI that figures out whether the connection you're sitting on is in an RKN/TSPU-blocked zone — and, more usefully, what kind of block it is (DNS poisoning, TCP reset, TLS DPI on SNI, or an ISP stub page).

The point isn't "site X doesn't open." Browsers already tell you that. The point is to look at each layer of the stack independently and report where it broke. That tells you a lot more about your situation than a generic "this site can't be reached" page.

Example output

rkn-check sample output

Same output as plain text
======================================================================
  RKN Block Checker
======================================================================
  IP:       95.165.xxx.xxx
  ISP:      AS12389 Rostelecom
  Location: Moscow, Moscow, RU
----------------------------------------------------------------------

Whitelist (should always work)
  name          verdict            TCP     TLS     PLT  status
  ------------------------------------------------------------
  gosuslugi     ✓ OK              18ms    42ms   380ms  200
  yandex        ✓ OK               8ms    25ms    95ms  200
  sberbank      ✓ OK              12ms    38ms   250ms  200
  vk            ✓ OK               9ms    28ms   180ms  200
  ...

Blacklist (RKN-restricted)
  name          verdict            TCP     TLS     PLT  status
  ------------------------------------------------------------
  instagram     ✗ TLS BLOCK       22ms       —       —  —
    └ TLS reset — DPI cutting on SNI (typical RKN/TSPU)
  twitter/x     ✗ TLS BLOCK       24ms       —       —  —
    └ TLS timeout — silent drop after ClientHello
  rutracker     ✗ HTTP STUB       18ms    45ms   120ms  200
    └ response body matches an ISP stub-page marker
  protonvpn     ✗ DNS BLOCK          —       —       —  —
    └ system DNS doesn't resolve, DoH does — DNS poisoning
  ...

======================================================================
  Summary
----------------------------------------------------------------------
  Whitelist: 21/21 working
  Blacklist: 3/15 open, 12/15 blocked

  → You ARE in an RKN-blocked zone.

  Block types in the blacklist:
    ✗ TLS BLOCK: 8
    ✗ DNS BLOCK: 2
    ✗ HTTP STUB: 2
======================================================================

Install

Python 3.10+.

pip install rkn-block-checker
rkn-check

Or from source:

git clone https://github.com/MayersScott/rkn-block-checker.git
cd rkn-block-checker
pip install -e .
rkn-check

Usage

rkn-check [-h] [--json] [--white] [--black] [--timeout TIMEOUT]
          [--workers WORKERS] [-v]
flag what it does
--json machine-readable JSON instead of the colored report
--white only the control (whitelist) targets
--black only the blacklist targets
--timeout per-probe timeout in seconds (default 5.0)
--workers thread pool size for parallel checks (default 10)
-v / -vv logging at INFO / DEBUG

JSON output

--json emits one object containing self_info (the IP/ISP block from the header) and the two result lists. Every result is the full per-target probe trace: which DNS resolver returned what, whether TCP and TLS succeeded with timings, the HTTP status, the verdict, and human-readable notes.

A trimmed sample (full version: docs/sample-output.json):

{
  "self_info": {
    "ip": "95.165.xxx.xxx",
    "city": "Moscow",
    "country": "RU",
    "org": "AS12389 Rostelecom"
  },
  "whitelist": [
    {
      "name": "gosuslugi",
      "url": "https://www.gosuslugi.ru/",
      "verdict": "OK",
      "notes": [],
      "sys_ip": "95.181.182.36",
      "doh_ip": "95.181.182.36",
      "dns_mismatch": false,
      "tcp_ok": true,  "tcp_time_ms": 18.4,
      "tls_ok": true,  "tls_time_ms": 42.1, "tls_cert_cn": "*.gosuslugi.ru",
      "status_code": 200, "plt_ms": 380.7
    }
  ],
  "blacklist": [
    {
      "name": "instagram",
      "url": "https://www.instagram.com/",
      "verdict": "TLS_BLOCK",
      "notes": ["TLS reset — DPI cutting on SNI (typical RKN/TSPU)"],
      "sys_ip": "157.240.20.174", "doh_ip": "157.240.20.174",
      "tcp_ok": true,  "tcp_time_ms": 22.4,
      "tls_ok": false, "tls_error": "connection reset by peer"
    },
    {
      "name": "protonvpn",
      "url": "https://protonvpn.com/",
      "verdict": "DNS_BLOCK",
      "notes": ["system DNS doesn't resolve, DoH does — DNS poisoning"],
      "sys_ip": null, "doh_ip": "185.70.40.182",
      "dns_error": "system resolver failed, DoH succeeded",
      "tcp_ok": false
    }
  ]
}

verdict is one of OK, DNS_BLOCK, TCP_RESET, TLS_BLOCK, HTTP_STUB, TIMEOUT, DOWN, or UNKNOWN. The probe trace fields (sys_ip, tcp_ok, tls_ok, etc.) are always present so you can tell why a verdict was reached — a TLS_BLOCK with tcp_ok: true is the DPI-on-SNI signature; one with tcp_ok: false would mean something else failed first.

Pipes nicely into jq:

# names of every blocked site
rkn-check --json | jq -r '.blacklist[] | select(.verdict != "OK") | .name'

# count by block type
rkn-check --json | jq '.blacklist | group_by(.verdict) | map({verdict: .[0].verdict, count: length})'

# only DPI-style blocks (TCP fine, TLS dies)
rkn-check --json | jq '.blacklist[] | select(.verdict == "TLS_BLOCK" and .tcp_ok)'

How it works

For each target the tool walks DNS → TCP → TLS → HTTP and stops at the first thing that fails. Whichever layer broke becomes the verdict.

layer probe what a failure means
DNS system resolver vs Cloudflare DoH if only the system fails, the ISP is poisoning DNS — the cheapest, oldest form of blocking
TCP plain TCP handshake on :443 a RST is IP-level blackholing. Rare — most ISPs don't bother
TLS TLS handshake with SNI = target host reset/timeout here (with TCP working fine) is the classic TSPU/DPI signature: the middlebox sees the SNI and tears the connection down
HTTP GET after handshake completes 451, or an ISP stub page returning 200 with a "blocked by RKN" body

Two probes are worth calling out:

System DNS vs DoH. The cheapest way to "block" a site is to make the ISP's DNS lie. Every host is resolved twice — once via socket (which uses whatever resolver the OS is configured for, usually the ISP's) and once via Cloudflare's DoH endpoint, which the ISP can't intercept. Disagreement is the smoking gun.

TLS handshake with SNI. Modern TSPU equipment doesn't drop the TCP connection — it lets you connect, reads the SNI extension out of the ClientHello, and then sends a RST or simply stops responding. So we have to actually start the TLS handshake to see this. A TLS_BLOCK after a clean TCP_OK is the unambiguous fingerprint of DPI-based blocking.

Layout

rkn_checker/
  __main__.py     # python -m rkn_checker
  cli.py          # argparse + entry point
  core.py         # orchestrates DNS -> TCP -> TLS -> HTTP
  dns.py          # system resolver + Cloudflare DoH
  network.py      # raw TCP and TLS probes
  http.py         # HTTP GET + stub-page detection
  output.py       # colored CLI report
  targets.py      # whitelist, blacklist, stub markers
  models.py       # CheckResult, Verdict
tests/            # pytest, all network calls mocked

Tests

pip install -e ".[dev]"
pytest

No network calls in the test suite — every probe is mocked, so it runs the same in CI, on a plane, or behind a corporate proxy.

Releasing

Releases are pushed to PyPI automatically by the release.yml workflow when a v* tag is pushed. The workflow uses PyPI Trusted Publishing — no API token in repo secrets.

One-time setup on PyPI: add a pending publisher pointing at this repo, the release.yml workflow, and the pypi environment. Then to ship 0.2.1:

# bump version in pyproject.toml first, commit
git tag v0.2.1
git push origin v0.2.1

The workflow checks that the tag matches pyproject.toml's version, builds sdist + wheel, runs twine check --strict, publishes to PyPI, and attaches the artifacts to a GitHub Release with auto-generated notes.

Caveats

  • IPv4 only. Some Russian ISPs treat IPv6 differently (often less filtered) but the v4 path is what users actually experience in practice.
  • The target lists are hard-coded (~20 sites per category). That's enough for a verdict but won't catch a block that affects only one specific resource. To extend — rkn_checker/targets.py.
  • One-shot snapshot, no retries, no longitudinal tracking. If you want to monitor a connection over time, run rkn-check --json from cron.
  • Stub markers are mostly Russian-language phrases; false positives on unrelated sites that happen to contain the same words are theoretically possible but I haven't seen one yet.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rkn_block_checker-0.2.1.tar.gz (16.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rkn_block_checker-0.2.1-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file rkn_block_checker-0.2.1.tar.gz.

File metadata

  • Download URL: rkn_block_checker-0.2.1.tar.gz
  • Upload date:
  • Size: 16.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rkn_block_checker-0.2.1.tar.gz
Algorithm Hash digest
SHA256 9a6a6ee0361c9502c97ec4a11e0e1dad2c555eef43b9860957b86f472f74c0a3
MD5 be4440a2ce68ada665fbda502cc52fb6
BLAKE2b-256 0651aa98bc21cd8cd274cdfb8c72c6c310f615546f8043e876381c0f34470e6b

See more details on using hashes here.

Provenance

The following attestation bundles were made for rkn_block_checker-0.2.1.tar.gz:

Publisher: release.yml on MayersScott/rkn-block-checker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rkn_block_checker-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for rkn_block_checker-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 140d49af75075f1266b0c94c47c5e0da5dfb6b638a675e6c5a2efeb44df4569e
MD5 2805285b444fd324b6635d338210b055
BLAKE2b-256 b500a0e01f4fc61ad388777b454a78a6129769a717b6eef68646cbd4963f6d14

See more details on using hashes here.

Provenance

The following attestation bundles were made for rkn_block_checker-0.2.1-py3-none-any.whl:

Publisher: release.yml on MayersScott/rkn-block-checker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page