DEV Community

Greta
Greta

Posted on

Is Your ISP Proxy Actually an ISP? Verifying ASN Claims in Python

Is Your ISP Proxy Actually an ISP? Verifying ASN Claims in Python

ISP proxies occupy a genuinely useful niche: IPs registered to consumer ISPs (Comcast, Verizon, Deutsche Telekom) but hosted in datacenters, so you get residential-looking ASN reputation with datacenter-like uptime and speed. They cost a premium over plain datacenter proxies — often 5–10x — and the entire premium is justified by one property: the IP's registration says "consumer ISP," not "hosting company."

Which raises an obvious question nobody seems to ask: how do you know the IP you were sold actually resolves to a consumer ISP ASN? Mislabeling is trivially easy — a vendor leases a datacenter range, calls it "ISP proxies," and charges ISP prices. The target site's risk engine sees an AS belonging to a hosting provider and blocks you exactly like a datacenter proxy, except you paid 8x for it.

You don't have to trust the label. ASN data is public. Here's how to verify it yourself, continuously, in Python.

The two lookups that matter

For every proxy IP you're sold, you want two facts:

  1. The announcing ASN — which autonomous system routes that IP (from BGP data).
  2. The ASN's type and owner — who runs that AS, and are they a consumer ISP, a hosting provider, a mobile carrier, or something else.

The first comes from BGP-looking-glass style APIs (Team Cymru's DNS whois service is the classic free one). The second comes from ASN metadata — Team Cymru again, or ipinfo's type classifications (isp, hosting, business, etc.). A residential-looking IP has an ASN whose owner is a household-name ISP and whose type is isp. Anything else — especially hosting — and your "ISP proxy" is a datacenter proxy wearing a costume.

Let's build the verifier.

ASN resolution via DNS

Team Cymru runs a free DNS-based WHOIS: reverse the IP octets, append .origin.asn.cymru.com, and TXT-record lookups return the announcing ASN. It's fast, needs no API key, and scales to thousands of lookups per minute.

import dns.resolver  # pip install dnspython

def resolve_asn(ip: str) -> str | None:
    """Return the announcing ASN for an IP via Team Cymru DNS."""
    rev = ".".join(reversed(ip.split(".")))  # 1.2.3.4 -> 4.3.2.1
    query = f"{rev}.origin.asn.cymru.com"
    try:
        answers = dns.resolver.resolve(query, "TXT")
        txt = answers[0].to_text().strip('"')
        # format: "23457 | 12.0.1.0/24 | US | arin | 2001-01-01"
        return txt.split("|")[0].strip()
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
            dns.resolver.LifetimeTimeout):
        return None
Enter fullscreen mode Exit fullscreen mode

Then the ASN's own metadata, again via DNS:

def asn_info(asn: str) -> dict:
    """Look up the ASN's registered name and country."""
    try:
        answers = dns.resolver.resolve(f"AS{asn}.asn.cymru.com", "TXT")
        txt = answers[0].to_text().strip('"')
        # "23457 | US | arin | 2001-01-01 | EXAMPLE-AS, US"
        parts = [p.strip() for p in txt.split("|")]
        return {"asn": parts[0], "country": parts[1],
                "name": parts[4] if len(parts) > 4 else ""}
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
            dns.resolver.LifetimeTimeout):
        return {"asn": asn, "country": "", "name": ""}
Enter fullscreen mode Exit fullscreen mode

Classifying: ISP or hosting?

The ASN name is a strong signal but a fuzzy one. Both lookups above are keyless and free, but for classification I prefer combining Cymru's name with a type field. ipinfo's free tier exposes an org/type view per IP; alternatively, maintain a small classification table of your own — the major consumer ISP ASNs per country are a finite, well-known set, and the major hosting ASNs (Amazon AS16509, Google AS15169, Hetzner AS24940, OVH AS16276, DigitalOcean AS14061...) are even more finite.

HOSTING_ASNS = {
    "16509": "Amazon", "14618": "Amazon", "396982": "Google Cloud",
    "15169": "Google", "24940": "Hetzner", "16276": "OVH",
    "14061": "DigitalOcean", "20473": "Vultr/Choopa", "9009": "M247",
    "51167": "Contabo", "63949": "Akamai/Linode", "45102": "Alibaba",
}

# Known consumer-ISP ASNs — extend per the countries you buy exits in
ISP_ASNS = {
    "7922": "Comcast", "701": "Verizon", "20057": "AT&T",
    "3320": "Deutsche Telekom", "3215": "Orange FR", "2856": "BT UK",
    "4713": "NTT JP", "4134": "Chinanet", "4837": "China Unicom",
}

def classify(ip: str) -> dict:
    asn = resolve_asn(ip)
    if asn is None:
        return {"ip": ip, "asn": None, "verdict": "unresolvable"}
    info = asn_info(asn)
    name = info.get("name", "")
    if asn in HOSTING_ASNS:
        verdict = "HOSTING — sold as ISP, resolves to datacenter ASN"
    elif asn in ISP_ASNS:
        verdict = "ISP — legit consumer ISP registration"
    elif any(k in name.lower() for k in
             ("hosting", "cloud", "server", "datacenter", "colo",
              "telecom hub", "ip volume", "leaseweb")):
        verdict = f"SUSPECT — name '{name}' looks infrastructure-grade"
    else:
        verdict = f"REVIEW — unknown ASN {asn}: {name}"
    return {"ip": ip, "asn": asn, "owner": name, "verdict": verdict}
Enter fullscreen mode Exit fullscreen mode

The REVIEW bucket matters. Plenty of legitimate ISP ASNs have odd names, and some gray-market "ISP" ranges are registered under holding companies whose names reveal nothing. The point of the audit isn't to adjudicate every ASN from your desk chair — it's to sort your purchased IPs into verified, definitely mislabeled, and needs human review.

Pulling exit IPs through the proxy

One practical wrinkle: to classify your proxy exits, you first need to see the IPs you're actually getting. Most gateways expose this through an IP-echo endpoint. The audit loop:

import requests

GATEWAY = "http://isp-user:pass.gate.thordata.com:9000"

def audit_exit() -> dict:
    s = requests.Session()
    s.proxies = {"http": GATEWAY, "https": GATEWAY}
    r = s.get("https://ipinfo.io/json", timeout=15)  # returns your exit IP
    exit_ip = r.json().get("ip")
    return classify(exit_ip)

def audit_pool(n=50):
    from collections import Counter
    verdicts = Counter()
    rows = []
    for _ in range(n):
        try:
            result = audit_exit()
        except requests.RequestException:
            result = {"verdict": "request-failed"}
        verdicts[result["verdict"].split("")[0]] += 1
        rows.append(result)
    print(verdicts.most_common())
    return rows
Enter fullscreen mode Exit fullscreen mode

Run this against 50 exits of an "ISP proxy" product and you get a distribution, not an anecdote. A clean product shows mostly ISP. A mislabeled product shows a wall of HOSTING. A mixed bag — some real ISP ASNs, some hosting, some unknown holding companies — is its own finding: it tells you the vendor aggregates from multiple sources of wildly different quality, and your traffic should be segregated accordingly.

Making it continuous

A one-shot audit decays immediately: ranges get re-leased, announcements change, and vendors rebalance. Fold the check into your pipeline as a cheap, continuous assertion — every time a worker grabs an exit, it already learns the exit IP from its first request; classify it then, cache the verdict by IP (ASN registrations change on month timescales, so a 24-hour cache is generous), and alert on verdict drift.

import time

_verdict_cache: dict[str, tuple[dict, float]] = {}
TTL = 86400

def classified_cached(ip: str) -> dict:
    hit = _verdict_cache.get(ip)
    if hit and time.time() - hit[1] < TTL:
        return hit[0]
    result = classify(ip)
    _verdict_cache[ip] = (result, time.time())
    return result
Enter fullscreen mode Exit fullscreen mode

If you're paying ISP-proxy prices, "the ASN drifted to hosting" is a billing event, not a curiosity — it's the moment you started paying 8x for datacenter-class reputation.

The wider lesson

Every property a proxy vendor sells — geography, ISP-ness, mobile-ness, exclusivity — is independently verifiable from public data, usually for free, usually in a few dozen lines of Python. ASN for ISP claims. GeoIP databases for country claims (and they disagree with each other, which is itself information about how your traffic looks to different defenses). Carrier checks for mobile claims.

Trust the product, but verify the invoice. The vendors who sell real ISP exits survive this audit happily; the ones who don't were never selling what you thought you bought.

Disclosure: I use Thordata's residential proxies for this project. New users get 500MB free — code thor020 (10% off): https://www.thordata.com/?ls=uXcSHJzx&lk=02-tele

Top comments (0)