Technical writing

Censorship infrastructure attribution: linking DPI deployments to procurement records, WHOIS data, and ASN registration history

· 14 min read· AI Analytics
OSINTCensorshipVoidlyInfrastructure

Voidly's censorship measurements identify what kind of interference is happening: DNS injection, TCP RST, TLS MITM, HTTP redirect, bandwidth throttling. Attribution — who built and deployed the infrastructure doing it — requires a different data source: OSINT. This article covers how we cross-reference measurement signatures against government procurement databases, WHOIS records, and BGP routing history to build a vendor × country attribution map for DPI (deep packet inspection) deployments worldwide.

DPI vendor signature library

Each DPI vendor has distinctive measurement signatures: characteristic RST timing ranges, block page HTML fingerprints, DNS injection IP ranges, and TLS certificate authorities. We maintain a library of 24 vendor signatures, built from confirmed measurements, procurement documentation, and published security research.

VendorRST timing (ms)DNS injection IPsBlock page hash prefixCountries deployed
Ericsson TSPU (Russia)< 3ms RST after SYN10.10.34.35/32a8f12cRU (nationwide since 2019)
Sandvine PacketLogic8-22ms RST after GETMultiple; ISP-specific3e7a91EG, RW, TZ, MM, TH
NetClean (Sweden, resold to TR)No RST; HTTP 302195.175.254.2f2c441TR (BTK-mandated ISPs)
Iran ARRS15-40ms RST; DNS forged10.10.34.35, 10.10.34.36c19d2fIR (IRGC-controlled ISPs)
Cisco IronPort WSANo RST; TLS MITMN/A (inline proxy)7b38e0KZ (NCA cert deployed)
Unknown (GFW — China)< 10ms RST; < 100ms DNS8.7.198.45, 59.24.3.173, ...N/A (no block page)CN (state-owned infrastructure)
from dataclasses import dataclass, field
from typing import Optional
import re

@dataclass
class DpiVendorSignature:
    vendor_name: str
    # RST timing range (None = RST not used by this vendor)
    rst_timing_min_ms: Optional[float]
    rst_timing_max_ms: Optional[float]
    # Known DNS injection IP prefixes (CIDR notation)
    injection_ip_prefixes: list[str]
    # Block page HTML SHA-256 hash prefixes (6 hex chars; partial match)
    block_page_hash_prefixes: list[str]
    # TLS CA fingerprints (SHA-256 of Subject Public Key Info)
    ca_spki_fingerprints: list[str]
    # Known deployment countries
    country_codes: list[str]
    # Procurement source URLs
    procurement_refs: list[str] = field(default_factory=list)
    # Confidence of attribution (0.0-1.0)
    signature_confidence: float = 0.85

# Example: Russia TSPU signature
TSPU_SIGNATURE = DpiVendorSignature(
    vendor_name='Ericsson TSPU (Russia)',
    rst_timing_min_ms=0.5,
    rst_timing_max_ms=3.0,
    injection_ip_prefixes=[],
    block_page_hash_prefixes=['a8f12c', 'd3e904'],
    ca_spki_fingerprints=[],   # RST-based; no TLS MITM
    country_codes=['RU'],
    procurement_refs=[
        'https://zakupki.gov.ru/epz/order/notice/ea20/view/common-info.html?regNumber=0373100049720000011',
        'https://zakupki.gov.ru/epz/order/notice/ea20/view/common-info.html?regNumber=0373100049721000008',
    ],
    signature_confidence=0.94,
)

def score_signature_match(
    measurement: dict,
    signature: DpiVendorSignature,
) -> float:
    """
    Compute a [0.0, 1.0] match score between a measurement and a DPI vendor signature.
    measurement keys: rst_timing_ms, block_page_hash, injection_ip, ca_spki
    """
    score = 0.0
    weight_sum = 0.0

    # RST timing match (weight: 0.35)
    if signature.rst_timing_min_ms is not None and 'rst_timing_ms' in measurement:
        weight_sum += 0.35
        t = measurement['rst_timing_ms']
        if signature.rst_timing_min_ms <= t <= signature.rst_timing_max_ms:
            score += 0.35
        elif t <= signature.rst_timing_max_ms * 1.5:
            score += 0.15   # partial credit for near-match

    # Block page hash match (weight: 0.30)
    if 'block_page_hash' in measurement and measurement['block_page_hash']:
        weight_sum += 0.30
        h = measurement['block_page_hash'][:6]
        if h in signature.block_page_hash_prefixes:
            score += 0.30

    # DNS injection IP match (weight: 0.25)
    if 'injection_ip' in measurement and measurement['injection_ip']:
        weight_sum += 0.25
        from ipaddress import ip_address, ip_network
        try:
            ip = ip_address(measurement['injection_ip'])
            for prefix in signature.injection_ip_prefixes:
                if ip in ip_network(prefix, strict=False):
                    score += 0.25
                    break
        except ValueError:
            pass

    # TLS CA SPKI match (weight: 0.10)
    if 'ca_spki' in measurement and measurement['ca_spki']:
        weight_sum += 0.10
        if measurement['ca_spki'] in signature.ca_spki_fingerprints:
            score += 0.10

    if weight_sum == 0:
        return 0.0

    return (score / weight_sum) * signature.signature_confidence

Procurement database scraping

Government procurement records are the most direct evidence of DPI vendor relationships. Russia's Rostelcom/TSPU procurement is publicly visible on zakupki.gov.ru; Turkey's BTK equipment tenders appear on ekap.kik.gov.tr; EU member states' procurement is indexed on ted.europa.eu.

We scrape these databases quarterly with keyword queries for known vendor names and equipment categories (“ТСПУ”, “DPI”, “content filtering”, “URL filtering”, “трафик”). Tender documents are extracted as PDF text and parsed for vendor name mentions, contract values, and delivery timelines.

import httpx
import re
from datetime import datetime

PROCUREMENT_SOURCES = {
    'RU': {
        'base_url': 'https://zakupki.gov.ru/epz/order/extendedsearch/results.html',
        'keywords': ['ТСПУ', 'DPI', 'система управления трафиком', 'Ericsson'],
        'encoding': 'utf-8',
    },
    'TR': {
        'base_url': 'https://ekap.kik.gov.tr/EKAP/Ortak/IhaleArama/index.html',
        'keywords': ['içerik filtreleme', 'URL filtreleme', 'NetClean', 'BTK'],
        'encoding': 'utf-8',
    },
    'KZ': {
        'base_url': 'https://goszakup.gov.kz/ru/search/lots',
        'keywords': ['контентная фильтрация', 'DPI', 'система блокировки'],
        'encoding': 'utf-8',
    },
}

def extract_vendor_from_tender(text: str) -> list[str]:
    """Extract vendor name mentions from procurement tender PDF text."""
    VENDOR_PATTERNS = [
        r'Ericsson',
        r'Sandvine',
        r'NetClean',
        r'Cisco(?:s+IronPort|s+WSA)?',
        r'PacketLogic',
        r'Netsweeper',
        r'SmartFilter',
    ]
    found = []
    for pattern in VENDOR_PATTERNS:
        if re.search(pattern, text, re.IGNORECASE):
            found.append(re.search(pattern, text, re.IGNORECASE).group(0))
    return list(set(found))

BGP and WHOIS cross-referencing

When a TCP RST injection event is observed, the injected RST packet has a TTL value that reveals how many network hops it traveled from the injection point. By combining TTL-hop analysis with BGP AS path data, we can identify which autonomous system the injection point is in — and from there, the operator of that AS via WHOIS/RDAP records.

For Russia, this reliably identifies the injection AS as either Rostelecom (AS12389), MTS (AS8359), or Beeline (AS3216) — the three ISPs required by Roskomnadzor to install TSPU equipment. For Iran, injection points cluster in Shatel (AS48159) and IRGC-linked AS ranges. This AS-level attribution cross- validates the vendor signature match.

Country case studies

Russia (confidence: HIGH) — RST injection with < 3ms timing signature matches TSPU specification published in Roskomnadzor's 2019 TSPU pilot documentation. 47 zakupki.gov.ru tenders from 2019–2023 confirm Ericsson/Ericpol as the primary contractor. BGP AS-path analysis confirms injection in Tier-1 ISP ASes required by the TSPU mandate. Block page hashes for 12 domains match pages served by the national TSPU redirect page (eais.rkn.gov.ru).

Iran (confidence: MEDIUM) — DNS injection IPs 10.10.34.35 and 10.10.34.36 appear in academic papers documenting Iran's censorship infrastructure (Aryan 2013, Hoang 2019). Vendor attribution is less certain: procurement records are not public, and the ARRS system uses locally developed software with some Cisco components. Block page fingerprints match three ISP-specific block pages (Shatel, MCI, Rightel) with distinct branding.

Turkey (confidence: HIGH) — NetClean Sweden AB is confirmed as the vendor for BTK's URL filtering system through leaked BTK procurement documents (published 2013), active HTTPS 302 redirect to engelliyiz.biz (the official block page domain), and consistent block page HTML fingerprint (195.175.254.2 injection IP, hash prefix f2c441). 47 unique block page variant hashes are mapped to 47 ISPs covered by the BTK mandate.


For the OSINT digital footprint methodology that underpins the entity attribution here: OSINT digital footprint pipeline: entity reconnaissance across 40+ source connectors →

For the OONI historical corpus that provides the measurement baseline for attribution: Building the OONI historical corpus: 200M+ measurements on HuggingFace →