iporigin: Tell a Datacenter IP from a Home Connection, Offline and With No API Key
"Is this visitor on a server or on a home connection?" comes up all the time. We ask it when scoring sign-ups, when filtering scrapers out of analytics, when deciding whether an abuse report is worth acting on, and when flagging logins that come from hosting ranges.
The usual answers are a paid API, which adds a network call, a key and a bill to every request, or a hand-maintained list of CIDRs that quietly goes stale within a month. We wanted a third option, so we built iporigin.
>>> import iporigin
>>> iporigin.classify("52.95.110.1")
Origin(ip='52.95.110.1', kind='hosting', provider='Amazon AWS', source='local')
>>> iporigin.is_datacenter("8.8.8.8")
True
>>> iporigin.is_datacenter("127.0.0.1")
False
No network calls, no signup, no runtime dependencies. The answer comes from a table bundled with the package, and a lookup takes about two microseconds.
What it tells you
Every address gets a kind, and a provider name when one is known:
| kind | meaning | examples |
|---|---|---|
hosting |
a machine in a cloud or hosting provider | AWS, Hetzner, OVHcloud |
cdn |
edge infrastructure in front of other people's sites | Cloudflare, Akamai |
vpn |
a consumer VPN or private relay exit | Mullvad, ProtonVPN, Apple Private Relay |
tor |
a Tor exit node | |
bot |
a declared crawler or an AI company's outbound IP | Googlebot, GPTBot, Meta |
reserved |
private, loopback, link-local, documentation | 127.0.0.1, 10.0.0.0/8 |
unknown |
in none of the lists, most often a residential ISP |
Two sets cover the common decisions, and they are deliberately different questions:
origin.kind in iporigin.DATACENTER_KINDS # a machine, not a home line
origin.kind in iporigin.ANONYMIZER_KINDS # vpn or tor
Someone browsing through Mullvad is a person at home, but the address they arrive from is a server, so it is in both sets. You may well want to rate-limit one and refuse the other.
unknown means absence of evidence. It is not a claim that the address is residential, and that
difference matters if you are about to block someone over it.
From the shell
The package installs an iporigin command, which reads addresses from its arguments or from
standard input:
$ iporigin 8.8.8.8 140.82.121.4 185.220.101.1 66.249.66.1 192.168.1.1
8.8.8.8 hosting Google
140.82.121.4 hosting GitHub
185.220.101.1 tor Tor
66.249.66.1 bot Googlebot
192.168.1.1 reserved
$ cut -d' ' -f1 access.log | iporigin --datacenter-only --json
{"ip": "34.82.1.5", "kind": "hosting", "provider": "Google Cloud", ...}
That second line is the one we reach for most: pipe a web server log through it and see which of
your "visitors" are really machines. In Python, iporigin.classify_many() does the same for a
batch and loads the table only once.
Where the data comes from
Most cloud providers publish their own address ranges, so the list can be compiled from source
instead of maintained by hand. iporigin's dataset has three tiers, and every source is recorded
with its licence in the repository's NOTICE file:
- Published by the provider. AWS, Google and Google Cloud, Azure, DigitalOcean, Linode, Vultr, Oracle Cloud, GitHub, Cloudflare and Fastly, each from the company's own feed.
- Community-maintained lists. Hetzner and OVH publish nothing machine-readable, and a large share of abusive traffic comes from exactly those two. Consumer VPN exits, Tor and crawler ranges have the same problem. These come from two public-domain (CC0) community repositories.
- Used with the maintainer's permission. Two further repositories have no licence file, which would normally rule them out. We asked their maintainers, and they said yes.
We only take what adds coverage. The best-known list of this kind was left out on purpose: every one of 211,616 sampled addresses from it was already covered, and a source that adds nothing is still one more endpoint that can break the rebuild.
Around 454,000 published prefixes collapse into roughly 40,000 disjoint IPv4 and IPv6 ranges
across 48 providers. A GitHub Action rebuilds the table every week, and a monthly release ships
it to PyPI when it has changed, so pip install --upgrade iporigin is never more than about a
month behind the feeds. The build also refuses to replace the dataset if it shrinks by more than
20%: a feed that suddenly answers with an empty body looks exactly like a provider giving up its
address space, and nothing else would catch it.
You don't have to trust our copy, either. python tools/build_dataset.py rebuilds it from the
same feeds.
Why the lookup is one bisect
The ranges are stored as sorted integer start/end pairs, so a lookup is one bisect and one
comparison. The part that matters is making them disjoint.
The feeds overlap: GitHub runs on Azure and AWS, so its prefixes sit inside theirs. A bisect
looks at exactly one candidate, and with overlapping ranges that candidate can be a narrow range
that ends just before the address while a wider one still contains it. The result would be
unknown for an address that is plainly in the table. So the build sweeps all ranges into a
disjoint partition, and where they overlap the narrowest one wins. An address in GitHub's range
inside Azure answers "GitHub", which is the more specific truth.
The table is loaded lazily on the first lookup, so importing the library and never calling it costs nothing.
Honest about the gaps
- Most consumer VPNs are not in the table. Only providers whose exits are publicly tracked
are. For broader coverage, the optional
iporigin.onlinemodule asks our free Unblock Master IP API, which runs its own VPN detection. It falls back to the offline answer if the request fails, so it is safe in a request path, and it is the only part of the library that ever touches the network. - Some provider-owned addresses are outside the published ranges.
1.1.1.1is Cloudflare's resolver, but it is not in Cloudflare's edge list, so it comes backunknown. - The data is as accurate as the feeds. A range reassigned yesterday is wrong until the next rebuild.
Install
pip install iporigin
Python 3.8 or later, no dependencies.
Give it a try
iporigin is MIT-licensed and the code is on GitHub: github.com/Yuix-Networks/iporigin. Issues and pull requests are welcome, especially pointers to providers that publish their ranges and are not in the table yet. And if it saves you an API bill, a star helps other people find it.