Skip to main content

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

Project description

RKN Block Checker

PyPI versionCI 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.3.1.tar.gz (22.0 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.3.1-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: rkn_block_checker-0.3.1.tar.gz
  • Upload date:
  • Size: 22.0 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.3.1.tar.gz
Algorithm Hash digest
SHA256 96ef7e7f6259f134274487a9e148fe1e1d2709e4394a7e4f54ab850fa4298e1c
MD5 a86283b91bc339d44883565e3e8c1998
BLAKE2b-256 9f99deafe08eeaaf257e4ab57cb4552033582f315eaeb252b88257f26502e323

See more details on using hashes here.

Provenance

The following attestation bundles were made for rkn_block_checker-0.3.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.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for rkn_block_checker-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9aea9ac1173b29842332f91cce3b01052f17b7c38edc3c0f510c131151b5be68
MD5 7067b955d4cce4bfec11a032aba201f1
BLAKE2b-256 069705aacd13db08f52b8eb0edebe76b2086fb3a3c5a080ef9ae1f1bff4516a3

See more details on using hashes here.

Provenance

The following attestation bundles were made for rkn_block_checker-0.3.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