DEV Community

Cover image for ASN Lookup for Security Engineers: From Concept to Code
ABDULLAH AFZAL
ABDULLAH AFZAL

Posted on

ASN Lookup for Security Engineers: From Concept to Code

An ASN lookup is the fastest way to find out who actually operates the network behind an IP address. Every publicly routable IP on the internet belongs to an autonomous system, and that autonomous system number (ASN) identifies the network operator, what infrastructure they run, and who they peer with. For security engineers, this is a first-order pivot point: when an IP shows up in your logs tied to credential stuffing or port scanning, the ASN tells you whether you're looking at a residential ISP, a cloud hosting provider, or a known bulletproof hosting operation.

This tutorial covers what ASNs are, how they fit into internet routing, and then gets to the practical part: querying ASN data programmatically with curl, Python, and JavaScript so you can fold it into your investigation workflow or SIEM pipeline.

TL;DR

  • An ASN (Autonomous System Number) uniquely identifies a network that participates in internet routing via BGP.
  • As of 2025, roughly 120,000 ASNs had been allocated globally, while the number actively visible in BGP routing tables varied by dataset and vantage point.
  • ASNs classify into three types: stub (one upstream), multi-homed (multiple upstreams), and transit (carries traffic for others).
  • Security engineers use ASN data for threat attribution, network-level blocking, infrastructure mapping, and log enrichment.
  • Programmatic ASN lookups return the network operator, organization type (ISP, hosting, business, education), route prefixes, and peering relationships.
  • This tutorial includes working code for ASN lookups in curl, Python, and JavaScript using ipgeolocation.io's ASN API.

In short: IP addresses identify devices, ASNs identify the networks those devices belong to. Knowing the network behind an IP is often more actionable than knowing the IP itself.

What Is an Autonomous System (and Why Does It Have a Number)

An autonomous system (AS) is a collection of IP address ranges, called prefixes, that operate under a single routing policy managed by one organization. An ISP like Comcast, a cloud provider like AWS, and a CDN like Cloudflare each operate their own autonomous systems. Internally, an AS can run whatever routing protocols it wants (OSPF, IS-IS, EIGRP). Externally, it presents one consistent set of rules about which IP prefixes it controls and how traffic reaches them.

Each AS gets a unique number: its ASN. These are assigned by IANA through five Regional Internet Registries (RIRs): ARIN (North America), RIPE NCC (Europe, Middle East, Central Asia), APNIC (Asia-Pacific), LACNIC (Latin America), and AFRINIC (Africa). The original 16-bit format allowed 65,536 ASNs. The 32-bit format, standardized in RFC 6793, extends that to over 4.2 billion. As of 2025, roughly 120,000 ASNs had been allocated globally, while the number actively visible in BGP routing tables varied by dataset and vantage point.

Three types matter in practice:

  • Stub AS: connects to only one upstream provider. A small company with a single ISP connection. Most ASNs are stubs.
  • Multi-homed AS: connects to two or more upstreams for redundancy. A mid-size enterprise with connections to both Cogent and Lumen.
  • Transit AS: carries traffic between other autonomous systems. The backbone providers (AS3356 Lumen, AS2914 NTT, AS174 Cogent) that form the internet's core routing infrastructure. The glue that holds all of this together is BGP (Border Gateway Protocol). When an AS wants the internet to know about its IP prefixes, it announces them via BGP. Each announcement includes an AS-PATH: the sequence of ASNs that traffic traverses to reach the prefix. Routers use the AS-PATH to choose the best route, typically the shortest one.

A concrete example: your browser requests a page hosted in Frankfurt. Your ISP (say, AS7922 Comcast) checks its BGP table and finds that the shortest path to the destination prefix goes through AS3356 (Lumen) to AS24940 (Hetzner). Three ASNs, three AS-path hops.

Why Security Engineers Care About ASN Data

Threat Attribution Beyond the IP

An IP address can change hands hourly in cloud environments. An ASN identifies the network operator, which is far more stable. When you see a cluster of malicious requests coming from 5 different IPs, they might all resolve to the same ASN. That's not 5 separate actors; that's one network.

Suppose your WAF flags credential stuffing attempts from 185.220.101.x. You look up the ASN: AS205100, F3 Netze e.V., a Tor exit node operator in Germany. That single fact reframes the incident. You're not dealing with a compromised residential user; you're dealing with traffic deliberately routed through an anonymization network. The response changes from "investigate the user" to "rate-limit the network."

Tip: The type field in ASN data is your first filter. HOSTING and ISP ASNs produce most bot traffic. EDUCATION and BUSINESS ASNs are more likely to be legitimate. It's a rough heuristic, not a rule, but it cuts noise.

ASN-Based Blocking and Allowlisting

Individual IP blocking is a game of whack-a-mole. Attackers rotate IPs constantly. ASN-level blocking is coarser but more durable: if an autonomous system consistently sources malicious traffic, blocking it at the network level eliminates entire IP ranges at once.

A practical example: hosting providers like Hetzner (AS24940), OVHcloud (AS16276), and DigitalOcean (AS14061) are popular with both legitimate developers and attackers. You probably can't block them outright. But you can apply stricter rate limits and CAPTCHA challenges to traffic originating from hosting ASNs while letting residential ISP ASNs through with less friction. The ASN's type field (ISP vs HOSTING vs BUSINESS) enables this segmentation without maintaining manual IP lists.

Infrastructure Mapping with Network Topology

Basic ASN lookups tell you who operates a network. Topology data, upstream and downstream relationships, tells you how that network connects to the rest of the internet.

When investigating a phishing campaign, you find the domain resolves to an IP in AS64500 (a small hosting provider). Pulling the downstream data reveals that AS64500 has three downstream ASNs, two of which have shown up in threat feeds before. That's not a coincidence; it's infrastructure reuse across campaigns. The upstream data shows AS64500 buys transit from a single provider, which gives you an escalation path for abuse reports.

This kind of analysis is impossible with IP-level data alone. It requires the network topology layer that ASN data provides.

SIEM and Log Enrichment

Raw firewall logs are walls of IP addresses. Enriching each IP with its ASN, organization name, and network type transforms those logs from "1,000 IPs hit the login endpoint" into "380 of those IPs belong to three hosting providers, 50 belong to Tor exit ASNs, and the rest are residential ISPs." That's actionable intelligence.

Adding ASN data to your SIEM pipeline lets you aggregate by network operator instead of individual IP. When AS16276 (OVHcloud) shows up in 200 failed login events across 40 different IPs, that's one signal, not 40.

How to Query ASN Data Programmatically

What You Need

  • An API key from ipgeolocation.io
  • curl (for quick lookups), Python 3.8+, or Node.js 18+ Alternatives exist in this space. Team Cymru, HackerTarget, ipinfo, and MaxMind GeoLite2 all offer ASN data. This tutorial uses ipgeolocation.io's ASN endpoint because it returns ASN details plus routes, peers, upstreams, and downstreams in a single call, which is the part that matters for security analysis.

Pitfall: ASN data from the unified IP geolocation endpoint on most providers gives you the basic AS number and organization. If you need route prefixes and peering relationships, you need a dedicated ASN endpoint or a separate BGP data source.

curl

Set your API key as an environment variable first:

export IPGEO_API_KEY="your_api_key_here"
Enter fullscreen mode Exit fullscreen mode

The examples below pipe output to jq for readability. Drop | jq . if you don't have it installed.

The fastest way to look up an IP's ASN:

curl -s "https://api.ipgeolocation.io/v3/asn?apiKey=${IPGEO_API_KEY}&ip=8.8.8.8" | jq .
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "ip": "8.8.8.8",
  "asn": {
    "as_number": "AS15169",
    "organization": "Google LLC",
    "country": "US",
    "type": "BUSINESS",
    "domain": "google.com",
    "date_allocated": "2000-03-10",
    "asn_name": "GOOGLE",
    "allocation_status": "ASSIGNED",
    "num_of_ipv4_routes": "1737",
    "num_of_ipv6_routes": "727",
    "rir": "ARIN"
  }
}
Enter fullscreen mode Exit fullscreen mode

Route counts and peering relationships change over time as BGP data evolves. The values above reflect a point-in-time snapshot.

To pull network topology for a specific ASN:

curl -s "https://api.ipgeolocation.io/v3/asn?apiKey=${IPGEO_API_KEY}&asn=AS24940&include=peers,upstreams,downstreams,routes" | jq .
Enter fullscreen mode Exit fullscreen mode

This returns the full picture: every IP prefix the AS announces, who it peers with, who provides its upstream transit, and who sits downstream. For AS24940 (Hetzner), the response includes announced IPv4 and IPv6 routes, plus the transit and peering relationships that connect Hetzner to the rest of the internet.

Python

Install the requests library if you haven't already:

pip install requests
Enter fullscreen mode Exit fullscreen mode
import os
import requests

API_KEY = os.environ.get("IPGEO_API_KEY")
if not API_KEY:
    raise RuntimeError("Missing IPGEO_API_KEY environment variable")

BASE_URL = "https://api.ipgeolocation.io/v3/asn"

def lookup_ip_asn(ip_address):
    """Resolve an IP to its ASN and network details."""
    try:
        resp = requests.get(
            BASE_URL,
            params={"apiKey": API_KEY, "ip": ip_address},
            timeout=(2.0, 5.0),
        )
        resp.raise_for_status()
        data = resp.json()

        asn_data = data.get("asn", {})
        return {
            "ip": data.get("ip"),
            "as_number": asn_data.get("as_number"),
            "organization": asn_data.get("organization"),
            "type": asn_data.get("type"),
            "country": asn_data.get("country"),
            "rir": asn_data.get("rir"),
        }
    except requests.exceptions.Timeout:
        print(f"Timeout looking up ASN for {ip_address}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"ASN lookup failed for {ip_address}: {e}")
        return None


def get_asn_topology(as_number):
    """Pull full network topology for an ASN: routes, peers, upstreams, downstreams."""
    try:
        resp = requests.get(
            BASE_URL,
            params={
                "apiKey": API_KEY,
                "asn": as_number,
                "include": "routes,peers,upstreams,downstreams",
            },
            timeout=(2.0, 10.0),
        )
        resp.raise_for_status()
        return resp.json().get("asn", {})
    except requests.exceptions.RequestException as e:
        print(f"Topology lookup failed for {as_number}: {e}")
        return None


if __name__ == "__main__":
    # Basic IP-to-ASN lookup
    result = lookup_ip_asn("8.8.8.8")
    if result:
        print(f"{result['ip']} -> {result['as_number']} ({result['organization']}, {result['type']})")

    # Full topology for a hosting provider
    topology = get_asn_topology("AS24940")
    if topology:
        routes = topology.get("routes", [])
        peers = topology.get("peers", [])
        upstreams = topology.get("upstreams", [])
        downstreams = topology.get("downstreams", [])
        print(f"AS24940 announces {len(routes)} routes, peers with {len(peers)} ASNs")
        print(f"  Upstreams: {[u['as_number'] for u in upstreams]}")
        print(f"  Downstreams: {[d['as_number'] for d in downstreams]}")
Enter fullscreen mode Exit fullscreen mode

The two functions cover the two core use cases: lookup_ip_asn for enriching individual IPs in a log pipeline, and get_asn_topology for deeper investigation when you need to understand a network's structure.

A longer timeout on the topology call (10 seconds vs 5) accounts for the larger response payload when routes and peering data are included.

JavaScript (Node.js)

const API_KEY = process.env.IPGEO_API_KEY;
if (!API_KEY) {
  throw new Error("Missing IPGEO_API_KEY environment variable");
}

const BASE_URL = "https://api.ipgeolocation.io/v3/asn";

async function lookupIpAsn(ipAddress) {
  const url = `${BASE_URL}?apiKey=${API_KEY}&ip=${encodeURIComponent(ipAddress)}`;

  try {
    const resp = await fetch(url, { signal: AbortSignal.timeout(5000) });

    if (!resp.ok) {
      console.error(`ASN lookup returned ${resp.status} for ${ipAddress}`);
      return null;
    }

    const data = await resp.json();
    const asn = data.asn ?? {};

    return {
      ip: data.ip,
      asNumber: asn.as_number,
      organization: asn.organization,
      type: asn.type,
      country: asn.country,
      rir: asn.rir,
    };
  } catch (err) {
    if (err.name === "TimeoutError") {
      console.error(`Timeout looking up ASN for ${ipAddress}`);
    } else {
      console.error(`ASN lookup failed for ${ipAddress}:`, err.message);
    }
    return null;
  }
}

async function getAsnTopology(asNumber) {
  const url = `${BASE_URL}?apiKey=${API_KEY}&asn=${encodeURIComponent(asNumber)}&include=routes,peers,upstreams,downstreams`;

  try {
    const resp = await fetch(url, { signal: AbortSignal.timeout(10000) });

    if (!resp.ok) {
      console.error(`Topology lookup returned ${resp.status} for ${asNumber}`);
      return null;
    }

    const data = await resp.json();
    return data.asn ?? {};
  } catch (err) {
    console.error(`Topology lookup failed for ${asNumber}:`, err.message);
    return null;
  }
}

// Usage
const result = await lookupIpAsn("8.8.8.8");
if (result) {
  console.log(`${result.ip} -> ${result.asNumber} (${result.organization}, ${result.type})`);
}

const topo = await getAsnTopology("AS24940");
if (topo) {
  const routes = topo.routes ?? [];
  const peers = topo.peers ?? [];
  console.log(`AS24940 announces ${routes.length} routes, peers with ${peers.length} ASNs`);
}
Enter fullscreen mode Exit fullscreen mode

Both functions mirror the Python version. AbortSignal.timeout(5000) prevents hung connections from blocking your pipeline. The ?? (nullish coalescing) operator handles missing fields without throwing.

Reading the Response: What Each Field Tells You

The basic ASN response contains eleven fields. Here's what matters for security work:

as_number: The ASN itself, formatted with the "AS" prefix (e.g., "AS15169"). This is the identifier you'll use for blocking rules, SIEM correlation, and threat feed matching.

organization: The entity registered with the RIR as the AS holder. For cloud providers, this is the provider, not the customer. AS16509 is "Amazon.com, Inc." even though the IP you're investigating belongs to a fintech startup running on AWS.

type: One of ISP, HOSTING, BUSINESS, or EDUCATION. This is your first-pass risk signal. HOSTING ASNs produce a disproportionate share of automated attacks. ISP ASNs carry mostly residential traffic. EDUCATION ASNs are universities.

country: ISO 3166-1 alpha-2 country code for the AS registration. Not necessarily where the traffic originates (a Hetzner server in Finland still shows "country": "DE" because Hetzner is registered in Germany).

rir: Which Regional Internet Registry manages the ASN. Useful for abuse reporting: RIPE NCC has different abuse processes than ARIN.

routes (with include=routes): The IP prefixes this AS announces to the internet. This is the AS's footprint. If you see malicious traffic from 5 IPs and all fall within prefixes announced by the same ASN, that confirms you're dealing with one network.

peers, upstreams, downstreams (with include=peers,upstreams,downstreams): The network's BGP relationships. Upstreams are transit providers (who the AS pays for internet access). Downstreams are customer networks. Peers are networks exchanging traffic directly. For investigation, downstreams are particularly interesting: they reveal smaller networks hiding behind a hosting provider's ASN.

Putting It Together: An Investigation Walkthrough

You pull your morning alerts and find 47 failed SSH login attempts across 3 IPs overnight: 185.220.101.34, 185.220.101.42, and 185.220.101.52. Different IPs, same /24 range, but you want to confirm this is one actor.

Step 1: Look up the ASN for any of the three IPs. They all resolve to AS205100, F3 Netze e.V., registered in Germany. The organization name and the related prefix data point to Tor-exit traffic. That reframes the incident immediately: you're not dealing with a compromised residential user, you're dealing with traffic deliberately routed through an anonymization network.

Step 2: Pull the topology with include=routes,upstreams. The routes show a small number of prefixes (the 185.220.101.0/24 range among them). The upstreams show two transit providers. A stub-like topology for what is effectively a single-purpose network.

Step 3: Check your SIEM for historical traffic from AS205100. You find it appears in 12 previous incidents over the past 90 days, all brute-force attempts. That's a pattern.

Step 4: Decision. Hard-blocking AS205100 is reasonable here because it's a single-purpose Tor exit network, and your service has no legitimate users routing through Tor exits. For a multi-purpose hosting ASN like Hetzner or DigitalOcean, you'd rate-limit instead of block.

The whole investigation took 4 API calls and 3 minutes. Without ASN data, you'd have looked up 3 IPs individually, found they're "in Germany," and missed the network-level pattern entirely.

Caching and Performance Notes

ASN assignments change infrequently. An IP's ASN changes when the IP block is sold or reallocated, which happens on the order of months or years, not hours. A 24-hour TTL on cached ASN lookups is aggressive enough for most security use cases without wasting quota on redundant calls.

For high-volume enrichment (processing millions of log entries), consider a local database instead of per-request API calls. MMDB-format ASN databases eliminate network latency entirely and support the same lookup patterns. The trade-off is update frequency: you're working with a daily snapshot instead of real-time data. For most enrichment pipelines, that's acceptable.

When the ASN API is unreachable, fail open. ASN enrichment adds context but isn't load-bearing for most security decisions. Log the unenriched event and backfill later rather than dropping traffic or blocking alerts because the lookup timed out.

A Few Extra Notes

Private ASNs exist. The ranges 64512-65534 (16-bit) and 4200000000-4294967294 (32-bit) are reserved for private use. These are used internally by organizations with complex routing but should never appear in public BGP tables. If you see a private ASN in external traffic, something is misconfigured upstream, and that's worth investigating on its own.

ASN data is one signal, not a verdict. AS16509 (Amazon AWS) hosts both legitimate SaaS products and attack infrastructure. Blocking it would break half the internet. Use ASN data to inform decisions (add friction, increase logging, require CAPTCHA), not to make them unilaterally.

The organization field can mislead. Cloud providers sublease IP ranges. The ASN registrant is Amazon, Google, or Microsoft, but the actual user of the IP is whoever rented that instance. For cloud ASNs, pair ASN data with reverse DNS, TLS certificate inspection, or company-level enrichment to get closer to the real operator.

For a deeper reference on autonomous systems, BGP routing, and ASN assignment history, the ARIN ASN guide and the ipgeolocation.io ASN guide both cover the conceptual side in more depth than this tutorial has room for.

Top comments (0)