Background¶
Notes on what each kind of lookup actually measures, and how much to trust it.
Geolocating an IP¶
There is no direct way to discern the physical location of an IP address. Locations are inferred from network delay and topology measurements combined with private and public databases. One family of algorithms starts from a set of landmarks at known locations, bounds the distance to the last router before the target using observed latency, intersects those bounds, and takes the centroid.
Accuracy is uneven and generally unquantified by the providers. Independent measurement studies consistently find that country-level assignment is reliable — error rates under about 1% across the major providers — while city-level accuracy varies by an order of magnitude, and is dramatically worse for mobile networks than for fixed ones. Treat country as solid, city as indicative, and latitude/longitude as a centroid rather than a location.
maxmind_geocode_ip reads the GeoLite2 City database locally.
Anonymous downloads ended in 2019: a free MaxMind account and license key are
now required.
Timezone¶
Timezone can be derived from coordinates, which means it inherits all the uncertainty above. Two paths are available:
maxmind_geocode_ipalready returnslocation.time_zonedirectly from the City database. This is free and requires no extra lookup.timezone_atresolves coordinates against timezone polygons offline viatimezonefinder(the optionaltimezoneextra). Useful as an independent cross-check.geonames_timezonequeries GeoNames over the network.
Ping and traceroute¶
ping sends ICMP echo requests and reports round-trip time (min, max, mean)
and packet loss. traceroute maps the routers along the path and the time to
each hop. Both shell out to the system commands and need no special
privileges. Many hosts drop ICMP entirely, so silence is not evidence of
absence.
Exposed services¶
Censys scans the IPv4 address space and reports open ports, protocols, certificates, and ASN. Requires registration; the free tier is credit-limited.
Shodan indexes internet-connected devices, services, and known vulnerabilities. IP lookups require a paid membership — free API keys cannot call the host endpoint.
Both report what a scanner observed at some past moment, not what is true now.
Reputation and abuse¶
Many organizations maintain blocklists, and they cite each other, so counting “detections” across services overstates independence.
VirusTotal aggregates verdicts from roughly ninety engines and reports ASN, network, RIR, JARM, and a reputation score derived from community votes. Note that VirusTotal returns categories only for domains and URLs, never for IP addresses.
AbuseIPDB collects user-submitted abuse reports with categories such as SSH brute force, port scanning, and web spam.
dayscontrols the lookback window, up to the API maximum of 365.APIVoid reports proxy, VPN, Tor, and hosting flags alongside blocklist detections.
An IP appearing on a blocklist says something about the address, which may be shared, reassigned, or NATed behind thousands of users. It is weak evidence about any individual.
Query limits¶
Service |
Free tier |
Notes |
|---|---|---|
GeoNames |
10,000/day, 1,000/hour |
Must enable the free web service |
AbuseIPDB |
1,000 checks/day |
Higher on paid plans |
VirusTotal |
500/day, 4/minute |
An unpublished monthly cap also applies |
Censys |
100 credits/month |
Platform API |
Shodan |
None |
IP lookup requires paid membership |
APIVoid |
None |
30-day trial only |
API reference¶
Know Your IP.
Collect data about IP addresses from multiple services:
Geolocation (latitude/longitude, country, city, timezone)
Reputation and abuse reports (AbuseIPDB, VirusTotal, APIVoid)
Exposed services and open ports (Shodan, Censys)
Network diagnostics (ping, traceroute)
- exception know_your_ip.ConfigurationError[source]¶
Bases:
ExceptionRaised when configuration cannot be loaded or fails validation.
- class know_your_ip.EnrichResult(records=<factory>, manifest=<factory>)[source]¶
Bases:
objectThe records from an enrichment run, plus how they were produced.
Returned rather than a bare DataFrame for two reasons:
pandasis an optional extra, so the core path must not require it; and the manifest needs somewhere to live that a plain list of dicts does not provide.- property columns: list[str]¶
Every field name present across the records, in first-seen order.
Providers return different fields for different addresses, so the union is what a table needs.
- Returns:
Ordered field names.
- property errors: dict[str, int]¶
How many records carry an error, per provider.
- Returns:
Provider name to affected record count.
- property canonical: list[dict[str, Any]]¶
one column per variable rather than per vendor.
Providers spell the same thing differently - country arrives about fifteen ways across five providers. This reduces them to canonical columns, each carrying which sources reported and whether they agreed.
- Returns:
One canonical record per address.
- Type:
The joined view
- tidy()[source]¶
Long form: one row per address, field, and source.
This is the shape for comparing sources - “which disagreed, and how” becomes a groupby rather than reading forty columns by eye.
- property disagreements: list[dict[str, Any]]¶
Every field where sources reported different values.
- Returns:
Rows of
{ip, field, chosen, values, sources}.
- to_dataframe(shape='canonical')[source]¶
Return the records as a pandas DataFrame.
- Parameters:
shape (str) –
"canonical"for the joined table (default),"raw"for every vendor-shaped field, or"tidy"for long form.- Returns:
A DataFrame.
- Raises:
ImportError – If the optional
pandasextra is not installed.ValueError – If
shapeis not one of the three known shapes.
- Return type:
- to_csv(path, columns=None, shape='canonical')[source]¶
Write the records to a CSV file.
- Parameters:
- Returns:
The path written.
- Raises:
ValueError – If
shapeis not one of the three known shapes.- Return type:
- exception know_your_ip.InvalidIPError[source]¶
Bases:
ValueErrorRaised when a value is not a valid IP address.
- class know_your_ip.KnowYourIPConfig(*, maxmind=<factory>, geonames=<factory>, abuseipdb=<factory>, ping=<factory>, traceroute=<factory>, timezone=<factory>, network=<factory>, rdap=<factory>, ranges=<factory>, apivoid=<factory>, censys=<factory>, shodan=<factory>, virustotal=<factory>, output=<factory>)[source]¶
Bases:
BaseModelTop-level configuration.
- Parameters:
maxmind (MaxMindConfig)
geonames (GeoNamesConfig)
abuseipdb (AbuseIPDBConfig)
ping (PingConfig)
traceroute (TracerouteConfig)
timezone (TimezoneConfig)
network (NetworkConfig)
rdap (RDAPConfig)
ranges (RangesConfig)
apivoid (APIVoidConfig)
censys (CensysConfig)
shodan (ShodanConfig)
virustotal (VirusTotalConfig)
output (OutputConfig)
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- maxmind: MaxMindConfig¶
- geonames: GeoNamesConfig¶
- abuseipdb: AbuseIPDBConfig¶
- ping: PingConfig¶
- traceroute: TracerouteConfig¶
- timezone: TimezoneConfig¶
- network: NetworkConfig¶
- rdap: RDAPConfig¶
- ranges: RangesConfig¶
- apivoid: APIVoidConfig¶
- censys: CensysConfig¶
- shodan: ShodanConfig¶
- virustotal: VirusTotalConfig¶
- output: OutputConfig¶
- know_your_ip.abuseipdb_api(config, ip)[source]¶
Check an IP address against AbuseIPDB.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the AbuseIPDB API key and lookback window.
ip (str) – An IP address.
- Returns:
Abuse fields prefixed with
abuseipdb..- Return type:
References
Example
>>> abuseipdb_api(config, "222.186.30.49")
- know_your_ip.apivoid_api(config, ip)[source]¶
Get IP reputation data from APIVoid.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the APIVoid API key.
ip (str) – An IP address.
- Returns:
Reputation fields prefixed with
apivoid..- Return type:
Note
Uses APIVoid API v2. The v1 endpoint reached its announced end of life in February 2026. v2 is a POST with an
X-API-Keyheader and returns the report at the top level rather than underdata.report.There is no permanent free tier - only a 30-day trial.
References
- know_your_ip.canonicalize(record)[source]¶
Reduce a record’s vendor-shaped fields to canonical columns.
- Parameters:
- Returns:
Canonical fields, each accompanied by
<field>.sourcesand, where more than one source reported,<field>.agree.<field>.valuesappears only when sources disagreed.- Return type:
Example
>>> canonicalize({"ip": "8.8.8.8", "censys.asn": 15169})["asn"] 15169
- know_your_ip.censys_api(config, ip)[source]¶
Get host data from the Censys Platform API.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the Censys base URL and API key.
ip (str) – An IP address.
- Returns:
Host fields prefixed with
censys..- Return type:
Note
Legacy Search (
search.censys.io) was disabled for free accounts in March 2025 and is deprecated entirely in September 2026. Authentication is a Personal Access Token.organization_idis optional and should be omitted on the free tier, which allows 100 credits/month.References
- know_your_ip.create_default_config(output_file)[source]¶
Write a default configuration file.
- Parameters:
output_file (Path) – Destination path. Parent directories are created.
- Return type:
None
- know_your_ip.enrich(ips, *, config=None, providers=None, as_of=None, cache=None, max_age=None, max_workers=5)[source]¶
Enrich many IP addresses concurrently.
Requests are paced to each provider’s published rate limit, so raising
max_workersdoes not produce rate-limit errors. Invalid addresses are skipped and recorded in the manifest rather than aborting the run, and a provider failing on one address does not affect the others.- Parameters:
ips (Iterable[str]) – The addresses to enrich.
config (KnowYourIPConfig | None) – Configuration. Loaded from the standard locations if omitted.
providers (list[str] | None) – Provider names to run. Defaults to those enabled in config.
as_of (date | None) – Ask what was true on this date. Providers without historical support are skipped rather than answering with current data.
cache (Cache | str | Path | None) – A
Cache, or a path to open one at. Re-running over cached addresses costs no API quota.max_age (timedelta | None) – How stale a cached observation may be before it is refetched.
max_workers (int) – Concurrent lookups.
- Returns:
An
EnrichResultholding the records and a run manifest.- Raises:
ValueError – If
max_workersis less than one.- Return type:
Example
>>> result = enrich(["8.8.8.8", "1.1.1.1"], providers=["network"]) >>> len(result) 2
- know_your_ip.enrich_csv(input_path, output_path=None, *, column=None, columns=None, **kwargs)[source]¶
Enrich the addresses in a file and optionally write a CSV.
- Parameters:
input_path (str | Path) – A text file of addresses, one per line, or a CSV.
output_path (str | Path | None) – Where to write results. No file is written if omitted.
column (str | None) – Column holding addresses when the input is a CSV with a header. If omitted, the file is read as one address per line.
columns (list[str] | None) – Column order for the output. Defaults to every field collected.
- Returns:
An
EnrichResult.- Return type:
Example
>>> enrich_csv("ips.csv", "out.csv", column="ip")
- know_your_ip.geonames_timezone(config, lat, lng)[source]¶
Get timezone information for a coordinate from GeoNames.
- Parameters:
config (KnowYourIPConfig) – Configuration object.
lat (float) – Latitude.
lng (float) – Longitude.
- Returns:
GeoNames fields prefixed with
geonames..- Return type:
Note
Free tier is 10,000 credits/day and 1,000/hour per username.
Example
>>> geonames_timezone(config, 32.0617, 118.7778)
- know_your_ip.load_config(config_file=None)[source]¶
Load configuration from a TOML file and environment variables.
Defaults are overridden by the file, which is overridden by environment variables.
- Parameters:
config_file (Path | None) – Path to a configuration file. If None, standard locations are searched.
- Returns:
A validated configuration object.
- Raises:
ConfigurationError – If the file cannot be read or validation fails.
- Return type:
- know_your_ip.maxmind_geocode_ip(config, ip)[source]¶
Look up an IP address in the MaxMind GeoLite2 City database.
- Parameters:
config (KnowYourIPConfig) – Configuration object.
ip (str) – An IP address.
- Returns:
Geolocation fields prefixed with
maxmind.. Empty if the address is absent from the database.- Raises:
FileNotFoundError – If
GeoLite2-City.mmdbis not at the configureddb_path.- Return type:
Note
Reads the database directly with
maxminddb. Thegeoip2wrapper removed itsrawattribute in 5.0 and pulls inaiohttp;maxminddb.Reader.get()returns the same underlying record with no additional dependencies.A MaxMind account and license key are required to download GeoLite2; anonymous downloads ended in 2019.
- know_your_ip.ping(config, ip)[source]¶
Measure round-trip time to an IP address.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding ping count and timeout.
ip (str) – An IP address.
- Returns:
Fields prefixed with
ping.. Timing keys are absent if the host did not respond.- Return type:
- know_your_ip.query_ip(config, ip, *, providers=None, as_of=None, cache=None, max_age=None)[source]¶
Collect data on an IP address from every selected provider.
Each provider is isolated: a failure in one is recorded under
<provider>.errorwithout preventing the others from running.- Parameters:
config (KnowYourIPConfig) – Configuration object.
ip (str) – An IP address.
providers (list[str] | None) – Provider names to run. Defaults to those enabled in
config.as_of (date | None) – Ask what was true on this date. Providers without historical support are skipped rather than answering with present-day data.
cache (Cache | None) – Cache to read from and append to, if any.
max_age (timedelta | None) – How stale a cached observation may be before it is refetched.
- Returns:
Every field collected, keyed by
<provider>.<field>. Useselect_columns()to restrict the result to a chosen subset.- Return type:
Example
>>> query_ip(config, "8.8.8.8") {'ip': '8.8.8.8', 'maxmind.country.names.en': 'United States', ...}
- know_your_ip.range_lookup(config, ip)[source]¶
Report which published networks an address belongs to.
- know_your_ip.select_columns(record, columns)[source]¶
Restrict a record to a set of columns.
query_ipreturns every field it collected. Use this when you want only the subset named inconfig.output.columns.
- know_your_ip.shodan_api(config, ip)[source]¶
Get host data from Shodan.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the Shodan API key.
ip (str) – An IP address.
- Returns:
Host fields prefixed with
shodan..- Raises:
ImportError – If the optional
shodanextra is not installed.- Return type:
Note
IP lookups require a paid membership; free API keys cannot call the host endpoint.
- know_your_ip.tidy(records)[source]¶
Reshape records into one row per (address, field, source).
The long form is what comparing sources actually wants, and what belongs in a paper’s appendix: it makes “which sources disagreed, and how” a groupby rather than a manual reading of forty columns.
- know_your_ip.timezone_at(config, lat, lng)[source]¶
Get the timezone name for a coordinate, offline.
- Parameters:
config (KnowYourIPConfig) – Configuration object.
lat (float) – Latitude.
lng (float) – Longitude.
- Returns:
An IANA timezone name, or None if the coordinate maps to no timezone.
- Raises:
ImportError – If the optional
timezoneextra is not installed.- Return type:
str | None
Note
Backed by
timezonefinder. The finder loads a polygon dataset on first use and is cached for the lifetime of the process.MaxMind’s City database already reports
location.time_zone, so this is useful mainly as an independent cross-check.Example
>>> timezone_at(config, 32.0617, 118.7778) 'Asia/Shanghai'
- know_your_ip.traceroute(config, ip)[source]¶
Trace the network path to an IP address.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the hop limit.
ip (str) – An IP address.
- Returns:
Fields prefixed with
traceroute..- Return type:
- know_your_ip.validate_ip(ip)[source]¶
Validate and normalize an IP address.
- Parameters:
ip (str) – Candidate IPv4 or IPv6 address.
- Returns:
The normalized string form of the address.
- Raises:
InvalidIPError – If
ipis not a valid IP address.- Return type:
Example
>>> validate_ip(" 8.8.8.8 ") '8.8.8.8'
- know_your_ip.virustotal_api(config, ip)[source]¶
Get an IP address report from VirusTotal API v3.
- Parameters:
config (KnowYourIPConfig) – Configuration object holding the VirusTotal API key.
ip (str) – An IP address.
- Returns:
Fields prefixed with
virustotal., including analysis counts, reputation, network metadata, and vote totals.- Return type:
Note
Public API limits are 500 requests/day and 4 requests/minute.
VirusTotal does not return a
categoriesattribute for IP address objects;categoriesexists only on domain and URL objects.References
https://docs.virustotal.com/reference/ip-info
Example
>>> virustotal_api(config, "8.8.8.8")
The join¶
The join: one column per variable, not one per vendor.
Providers describe the same things in different words. For a single address,
MaxMind reports country.iso_code plus country names in ten languages, Censys
reports country_code, AbuseIPDB country_code, VirusTotal country,
and RDAP country - about fifteen columns naming three different concepts.
Reconciling that is work every user would otherwise repeat.
This module maps provider fields onto canonical names, and records whether the sources agreed. Disagreement is reported rather than resolved away: geolocation sources genuinely differ, and a table that hides that is worse than one that shows it.
The mapping is deliberately conservative. Two fields merge only when they mean
the same thing, and several near-misses are kept apart on purpose - see
SEPARATE_BY_DESIGN.
- know_your_ip.schema.canonicalize(record)[source]¶
Reduce a record’s vendor-shaped fields to canonical columns.
- Parameters:
- Returns:
Canonical fields, each accompanied by
<field>.sourcesand, where more than one source reported,<field>.agree.<field>.valuesappears only when sources disagreed.- Return type:
Example
>>> canonicalize({"ip": "8.8.8.8", "censys.asn": 15169})["asn"] 15169
- know_your_ip.schema.canonical_columns()[source]¶
Every column
canonicalize()can produce, in registry order.
Published network ranges¶
Membership in published network ranges: clouds, CDNs, Tor exits, crawlers.
Tor’s exit list, AWS’s ip-ranges.json, GCP’s cloud.json, Cloudflare’s
address list, Fastly’s, and Google’s and Bing’s crawler ranges are all the same
operation - fetch a published list of CIDRs and test membership. That is one
implementation, not six.
Every source here is the operator’s own published file: free, keyless, and authoritative in a way a third-party guess about “is this a datacenter” is not. For research the question these answer is often the real one - whether an address is a person or a machine.
Lists are cached on disk, so a batch run costs one fetch per source rather than one per address.
- class know_your_ip.ranges.RangeSource(name, url, kind, parser)[source]¶
Bases:
objectA published list of network ranges.
- class know_your_ip.ranges.RangeIndex[source]¶
Bases:
objectMembership lookup over many networks.
AWS alone publishes around seven thousand prefixes, so scanning every network for every address does not survive a real batch: ten thousand addresses would be seventy million comparisons. Networks are bucketed by their first octet, which reduces a lookup to a few dozen comparisons.
- know_your_ip.ranges.build_index(ttl=86400, sources=None)[source]¶
Fetch and index every configured source.
- Parameters:
ttl (int) – Seconds a cached list stays usable.
sources (tuple[RangeSource, ...] | None) – Sources to index. Defaults to
SOURCES, resolved at call time rather than bound as a default argument so the module-level list stays overridable.
- Returns:
The populated index. Sources that could not be fetched are skipped with a warning rather than failing the run.
- Return type:
- know_your_ip.ranges.get_index(ttl=86400)[source]¶
Return the process-wide index, building it on first use.
- Parameters:
ttl (int) – Seconds a cached list stays usable. A different value than the index was built with rebuilds it, so the setting is not silently ignored after the first call.
- Returns:
The shared index.
- Return type: