A Go package for IP address intelligence. Given any IPv4 or IPv6 address it determines whether the address belongs to a VPN provider, a proxy (public or web), a Tor exit node, or a hosting/data-centre range, derives a structured threat level from those signals, and returns geolocation and ASN metadata.
Designed for embedding in backend services: the Client is safe for
concurrent use, all detection runs locally against three bundled binary
databases, and the public API is intentionally small.
- Features
- How it works
- Data sources
- Getting started
- API reference
- Threat scoring
- CLI tool
- Tradeoffs
- Performance
- Known limitations
- Possible improvements
| Signal | Detection mechanism |
|---|---|
| VPN | IP2Proxy database (proxy type VPN) |
| Public / web proxy | IP2Proxy database (proxy types PUB, WEB, RES) |
| Tor exit node | IP2Proxy database + Tor Project DNSBL |
| Hosting / data centre | IP2Proxy database (DCH) + MaxMind GeoLite2-ASN (curated ASN list) |
| ASN number and route | MaxMind GeoLite2-ASN database |
| ASN organisation name | MaxMind GeoLite2-ASN database |
| Country and city | MaxMind GeoLite2-City database |
| Private / reserved IPs | Built-in RFC range check (no database I/O) |
| Threat level | Weighted score → None / Low / Medium / High / Critical |
Before any lookup the package checks whether the address falls inside a
private or reserved range (RFC 1918, loopback, link-local, CGNAT, test nets,
etc.). If it does, the call returns immediately with ThreatNone and all
flags false. This avoids unnecessary work for addresses that will never
appear in a database and is the common case in services that also receive
internal traffic from load-balancers or health checks.
The address is looked up in the binary IP2Proxy database. The database assigns each address a single primary proxy type:
| Type | Meaning | Flag set |
|---|---|---|
VPN |
VPN anonymizer | IsVPN |
TOR |
Tor exit node | IsTor |
DCH |
Data centre / hosting range | IsHosting |
PUB |
Public proxy | IsProxy |
WEB |
Web proxy | IsProxy |
RES |
Residential proxy (PX10+) | IsProxy |
SES |
Search-engine spider | (no flag) |
- |
Not a proxy | (no flag) |
Because IP2Proxy assigns one type per address, the flags IsVPN, IsProxy,
IsTor, and IsHosting are mutually exclusive when set by this database
alone.
The address is also looked up in the GeoLite2-ASN database using the
low-level maxminddb reader, which additionally returns the announced
network prefix (route) alongside the record. Three things happen here:
Hosting detection: IsHosting is set to true if the IP's ASN appears
in the package's built-in curated list of known hosting and cloud provider
ASNs (DigitalOcean, AWS, GCP, Azure, Hetzner, Vultr, OVH, Cloudflare, and
many others). The final IsHosting flag is the union of both databases.
ASN metadata: The ASN number (e.g. 15169) and organisation name (e.g.
"Google LLC") are exposed as Result.AsnNumber and Result.AsnOrg.
Route: The announced network prefix in CIDR notation (e.g. "8.8.8.0/24")
is exposed as Result.AsnRoute.
The address is looked up in the GeoLite2-City database to obtain the
full country name, ISO 3166-1 alpha-2 country code, and city name. These are
exposed as Result.Country, Result.CountryCode, and Result.City.
After all database lookups, if the address has not already been identified as a Tor exit node and the Tor DNS check is enabled (the default), the package queries the Tor Project's authoritative DNSBL:
<d>.<c>.<b>.<a>.dnsel.torproject.org
A response of 127.0.0.2 confirms the address is a known Tor exit node. The
lookup honours the context.Context passed to Check/CheckString, so
deadlines and cancellation work as expected.
The DNS check intentionally complements the database: the DNSBL is updated in near-real-time and therefore catches newly activated exit nodes that have not yet appeared in the monthly database snapshot. If the lookup fails for any reason (timeout, SERVFAIL, no network) the error is silently discarded and the database result stands — the check is best-effort.
The Tor DNSBL does not support IPv6. IPv6 Tor exit nodes are detected by the databases only.
The four boolean flags are combined into a numeric score, which is mapped to a named threat level. See Threat scoring below.
URL: https://lite.ip2location.com License: Creative Commons Attribution-ShareAlike 4.0 Cost: Free (registration required) Recommended tier: PX2 Approximate size: ~6 MB Update cadence: Monthly
IP2Proxy is the primary source for VPN, proxy, and Tor classification. The
free LITE PX2 tier provides proxy type, ISP, and country fields and is
sufficient for the core detection signals. Higher tiers (PX5+) additionally
populate the FraudScore field in Result. A paid .BIN file of any tier
can be dropped in without code changes.
URL: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data License: Creative Commons Attribution-ShareAlike 4.0 Cost: Free (registration required) Approximate size: ~9 MB Update cadence: Weekly
GeoLite2-ASN maps IP ranges to ASN numbers, organisation names, and announced
network prefixes. The package uses it for: detecting hosting providers via a
curated ASN list, and populating the AsnNumber, AsnOrg, and AsnRoute
metadata fields. The weekly update cadence makes it one of the freshest free
sources for ASN data.
Note: The GeoLite2-ASN free tier does not include a separate ISP name or a domain field.
ASNInfo.ISPis set to the same value asASNInfo.Org.ASNInfo.Domainis always empty; it requires a paid MaxMind domain database.
URL: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data License: Creative Commons Attribution-ShareAlike 4.0 Cost: Free (registration required) Approximate size: ~70 MB Update cadence: Weekly
GeoLite2-City maps IP ranges to country and city. The package uses it to
populate Result.Country, Result.CountryCode, and Result.City. The same
MaxMind account used for GeoLite2-ASN gives access to this database.
URL: https://www.torproject.org/ Cost: Free Requires: Outbound DNS resolution (IPv4 only) Update cadence: Near-real-time
The Tor Project maintains an authoritative DNSBL of active exit nodes. It
provides the most up-to-date Tor classification available and complements the
monthly IP2Proxy snapshot. Enabled by default; disable with
WithTorDNSCheck(false) for fully offline operation.
-
Go 1.21 or later.
-
IP2Proxy LITE PX2 database. Download
IP2PROXY-LITE-PX2.BIN(~6 MB) from https://lite.ip2location.com. Free registration required. -
MaxMind GeoLite2-ASN database. Download
GeoLite2-ASN.mmdb(~9 MB) from https://dev.maxmind.com/geoip/geolite2-free-geolocation-data. Free registration required. -
MaxMind GeoLite2-City database. Download
GeoLite2-City.mmdb(~70 MB) from the same MaxMind page. Uses the same free account. -
Keep all databases current. IP2Proxy is updated monthly; both MaxMind databases are updated weekly. Schedule periodic downloads and restart the service to load the new files.
go get github.com/bobadilla-tech/go-ip-intelligence/ipipackage main
import (
"context"
"fmt"
"log"
"github.com/bobadilla-tech/go-ip-intelligence/ipi"
)
func main() {
client, err := ipi.New(
ipi.WithDatabasePath("/etc/ip2proxy/IP2PROXY-LITE-PX2.BIN"),
ipi.WithASNDatabasePath("/etc/maxmind/GeoLite2-ASN.mmdb"),
ipi.WithCityDatabasePath("/etc/maxmind/GeoLite2-City.mmdb"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.CheckString(context.Background(), "134.122.0.1")
if err != nil {
log.Fatal(err)
}
fmt.Printf("VPN: %v\n", result.IsVPN)
fmt.Printf("Proxy: %v\n", result.IsProxy)
fmt.Printf("Tor: %v\n", result.IsTor)
fmt.Printf("Hosting: %v\n", result.IsHosting)
fmt.Printf("Threat: %s\n", result.Threat)
fmt.Printf("ASN: AS%d\n", result.AsnNumber)
fmt.Printf("ASN Org: %s\n", result.AsnOrg)
fmt.Printf("Route: %s\n", result.AsnRoute)
fmt.Printf("Country: %s (%s)\n", result.Country, result.CountryCode)
fmt.Printf("City: %s\n", result.City)
}Use CheckASN / CheckASNString when you only need network and ASN
information without the full threat assessment:
info, err := client.CheckASNString(context.Background(), "8.8.8.8")
if err != nil {
log.Fatal(err)
}
fmt.Printf("ASN: %s\n", info.ASN) // AS15169
fmt.Printf("Org: %s\n", info.Org) // Google LLC
fmt.Printf("ISP: %s\n", info.ISP) // Google LLC
fmt.Printf("Route: %s\n", info.Route) // 8.8.8.0/24
fmt.Printf("Type: %s\n", info.Type) // hostingclient, err := ipi.New(
ipi.WithDatabasePath("/etc/ip2proxy/IP2PROXY-LITE-PX2.BIN"),
ipi.WithASNDatabasePath("/etc/maxmind/GeoLite2-ASN.mmdb"),
ipi.WithCityDatabasePath("/etc/maxmind/GeoLite2-City.mmdb"),
ipi.WithTorDNSCheck(false),
)ctx, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)
defer cancel()
result, err := client.CheckString(ctx, ipStr)The timeout applies to the Tor DNS lookup only. All database lookups are in-memory and are not affected by the context.
Creates a new Client. WithDatabasePath, WithASNDatabasePath, and
WithCityDatabasePath are all required. Returns an error if any path is
missing or if any file cannot be opened. Previously opened handles are closed
before returning on failure.
Releases the file handles held by all three databases. Call when the Client
is no longer needed (typically via defer).
Analyses a net.IP value and returns the full intelligence result, including
threat signals, ASN metadata, and geolocation.
Private and reserved addresses return immediately with all flags false and
Threat set to ThreatNone.
Convenience wrapper around Check that accepts an IP address string. Returns
an error if the string is not a valid IPv4 or IPv6 address.
Returns structured ASN and network information for the given IP. Intended for dedicated ASN lookup endpoints. Private and reserved addresses return an error — they have no meaningful public ASN.
The Type field is derived from proxy and ASN signals: "hosting", "vpn",
"proxy", or "residential" (in priority order).
Convenience wrapper around CheckASN that accepts an IP address string.
type Result struct {
IP net.IP // 16-byte canonical form
IsVPN bool // IP2Proxy: proxy type VPN
IsProxy bool // IP2Proxy: proxy type PUB, WEB, or RES
IsTor bool // IP2Proxy and/or Tor DNSBL
IsHosting bool // IP2Proxy DCH or ASN in curated hosting list
Score int // raw weighted score (see Threat scoring)
Threat ThreatLevel // None / Low / Medium / High / Critical
FraudScore int // 0–100; 0 means unavailable (requires IP2Proxy PX5+)
AsnOrg string // ASN organisation name (e.g. "Google LLC")
AsnNumber uint // ASN number (e.g. 15169); 0 if unavailable
AsnRoute string // announced network prefix (e.g. "8.8.8.0/24")
Country string // full country name in English (e.g. "United States")
CountryCode string // ISO 3166-1 alpha-2 code (e.g. "US")
City string // city name in English (e.g. "Mountain View")
}Returned by CheckASN / CheckASNString. Suitable for direct use in API
responses.
type ASNInfo struct {
IP string // queried address (e.g. "8.8.8.8")
ASN string // formatted ASN (e.g. "AS15169"); empty if unavailable
Org string // organisation name (e.g. "Google LLC")
ISP string // ISP name; equals Org with GeoLite2-ASN free tier
Domain string // always "" with GeoLite2-ASN free tier
Route string // announced prefix (e.g. "8.8.8.0/24")
Type string // "hosting" | "vpn" | "proxy" | "residential"
}const (
ThreatNone ThreatLevel = 0
ThreatLow ThreatLevel = 1
ThreatMedium ThreatLevel = 2
ThreatHigh ThreatLevel = 3
ThreatCritical ThreatLevel = 4
)ThreatLevel implements fmt.Stringer: result.Threat.String() returns
"None", "Low", "Medium", "High", or "Critical".
| Option | Default | Description |
|---|---|---|
WithDatabasePath(path string) |
(required) | Path to the IP2Proxy .BIN file |
WithASNDatabasePath(path string) |
(required) | Path to the GeoLite2-ASN .mmdb file |
WithCityDatabasePath(path string) |
(required) | Path to the GeoLite2-City .mmdb file |
WithTorDNSCheck(enabled bool) |
true |
Enable/disable the Tor DNSBL lookup |
Each detected signal contributes a fixed number of points to the score. The score is then mapped to a named level.
| Signal | Points | Rationale |
|---|---|---|
| Tor exit node | 3 | Strongest anonymisation intent; exit node operators explicitly route others' traffic |
| VPN | 2 | Strong anonymisation signal |
| Proxy | 2 | Strong anonymisation signal |
| Hosting / DCH | 1 | Context signal only; many legitimate services run on cloud infrastructure |
Hosting is intentionally weighted low. A large fraction of legitimate API traffic originates from cloud providers (automated jobs, backend services, CI pipelines). Flagging it alone would generate significant false positives. It is included so that hosting combined with any other signal escalates the level accordingly.
| Score | Level | Typical causes |
|---|---|---|
| 0 | None |
Regular residential or business ISP |
| 1 | Low |
Hosting / cloud IP only |
| 2–3 | Medium |
VPN, proxy, or Tor (database only) |
| 4–5 | High |
Two signals active (e.g. VPN + hosting) |
| ≥ 6 | Critical |
Three or more signals |
A command-line tool is included for testing and one-off lookups.
go build -o ipi ./cmd/ipiipi -db <proxy-db> -asn-db <asn-db> -city-db <city-db> <ip> [<ip>...]| Flag | Default | Description |
|---|---|---|
-db |
(required) | Path to the IP2Proxy .BIN database file |
-asn-db |
(required) | Path to the GeoLite2-ASN .mmdb file |
-city-db |
(required) | Path to the GeoLite2-City .mmdb file |
-no-tor-dns |
false |
Disable the Tor DNSBL lookup |
IP: 134.122.0.1
VPN: false
Proxy: false
Tor: false
Hosting: true
Score: 1
Threat: Low
ASN: AS14061
ASN Org: DIGITALOCEAN-ASN
Route: 134.122.0.0/20
Country: Netherlands (NL)
City: Amsterdam
---
IP: 185.220.101.1
VPN: false
Proxy: false
Tor: true
Hosting: false
Score: 3
Threat: Medium
ASN: AS200052
ASN Org: RETN-AS
Route: 185.220.100.0/22
Country: Germany (DE)
City: Frankfurt am Main
---
IP: 192.168.1.1
VPN: false
Proxy: false
Tor: false
Hosting: false
Score: 0
Threat: None
---
This package performs all lookups locally with no calls to any external HTTP API at query time.
Advantages:
- No per-request latency from an external service.
- No API rate limits, quotas, or costs per query.
- Works fully offline (except the Tor DNS check, which is opt-out).
- No dependency on a third party's uptime at request time.
- Data never leaves your infrastructure.
Disadvantages:
- Coverage is frozen at the last database download.
- Three database files must be managed and refreshed out-of-band.
- The free LITE tiers have lower coverage than paid commercial products.
External APIs (e.g. ipinfo.io, IPQualityScore) typically have more up-to-date coverage and richer signals, but introduce network latency on every request and may have rate-limit and cost implications. This package deliberately prioritises offline reliability over coverage breadth.
Using three complementary databases produces richer results than any single
source: IP2Proxy for threat signals, GeoLite2-ASN for network metadata and
secondary hosting detection, and GeoLite2-City for geolocation. The cost is an
additional ~79 MB of memory and two extra in-memory lookups per Check call.
Use CheckASN / CheckASNString when you only need ASN and network type
information and do not need threat scoring or geolocation. This saves the city
database lookup. Use Check / CheckString for the full picture.
The hosting ASN list is hardcoded and covers the most widely encountered providers. It will not detect every hosting provider, particularly:
- Smaller regional providers not in the list.
- New providers whose ASN was assigned after the last list update.
- Providers that have reassigned or renumbered their ASNs.
This is a deliberate tradeoff: a conservative list minimises false positives (flagging legitimate ISP traffic as hosting). The GeoLite2-ASN database itself is updated weekly, but the ASN list embedded in the package only changes with a new package release.
| Aspect | LITE PX2 (free) | Paid tiers |
|---|---|---|
| VPN detection | Good | Excellent |
| Proxy detection | Good | Excellent |
| Tor detection | Good | Excellent |
| Residential proxies | Not included | PX10+ |
| Fraud score | Not included | PX5+ |
| Update cadence | Monthly | Monthly |
A paid .BIN file of any tier can be dropped in without code changes.
FraudScore populates automatically with PX5+.
All three databases are loaded into memory at startup. All subsequent lookups are in-memory operations with no disk I/O. Typical combined lookup latency for all three databases is in the low-microsecond range.
Client is safe for concurrent use by multiple goroutines. All underlying
libraries (IP2Proxy, maxminddb, geoip2) are thread-safe.
The Tor DNS check adds one DNS round-trip per Check call for non-Tor
addresses (1–5 ms on a typical cloud instance). If the address is already
identified as Tor by the database, the check is skipped. CheckASN never
performs a Tor DNS check.
Mitigations:
-
Set a context deadline — the DNS lookup respects the context:
ctx, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond) defer cancel()
-
Disable the check with
WithTorDNSCheck(false)for sub-millisecond requirements. -
Cache results externally (see Possible improvements).
| Database | Approximate size |
|---|---|
| IP2Proxy LITE PX2 | ~6 MB |
| MaxMind GeoLite2-ASN | ~9 MB |
| MaxMind GeoLite2-City | ~70 MB |
| Total | ~85 MB |
-
IPv6 Tor detection is database-only. The Tor Project's DNSBL does not support IPv6 queries. IPv6 Tor exit nodes are only detected if they appear in the IP2Proxy database.
-
Database freshness. VPN, proxy, and hosting detections are as current as the last database download. The Tor DNS check partially offsets this for Tor exit nodes.
-
Single proxy type per address (IP2Proxy). IP2Proxy assigns one primary type per address. A VPN server on cloud infrastructure is classified as
VPN, not alsoDCH. The GeoLite2-ASN lookup mitigates this forIsHostingspecifically. -
Curated ASN list coverage. Only ASNs in the built-in list trigger
IsHostingvia the ASN path. Providers not on the list are only detected if IP2Proxy classifies them asDCH. -
No residential proxy detection at the LITE tier. Requires IP2Proxy PX10 or higher.
-
Database reload requires restart. All databases are opened once at
Newtime. Loading updated files requires closing and re-creating theClient. -
DNS errors are silently swallowed. Tor DNSBL failures fall back to the database result with no indication in
Result. A sustained DNS outage silently degrades Tor detection to database-only coverage. -
No ISP/domain separation with free MaxMind databases.
ASNInfo.ISPequalsASNInfo.OrgandASNInfo.Domainis always empty. A paid MaxMind GeoIP2 ISP or Domain database would provide distinct values.
At high request rates the same addresses recur frequently. Wrapping
Check/CheckString with an in-memory LRU cache keyed by IP string would
eliminate redundant database lookups and the Tor DNS round-trip for recently
seen addresses. Libraries such as
github.com/hashicorp/golang-lru/v2
integrate cleanly.
The Client could watch the database files for changes (using fsnotify)
and hot-swap the underlying database handles under a read-write mutex, removing
the need for a service restart on monthly/weekly database updates.
The Tor Project publishes a full exit node list at
https://check.torproject.org/torbulkexitlist. Periodically downloading and
indexing this list in memory would add IPv6 Tor detection and allow fully
offline Tor classification with near-real-time freshness.
Upgrading to a paid MaxMind GeoIP2 ISP or Domain database would populate
ASNInfo.ISP with a distinct ISP name (separate from the org) and
ASNInfo.Domain with the primary domain associated with the ASN.