openasn 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.simplecov +25 -0
- data/AGENTS.md +23 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +242 -0
- data/Rakefile +34 -0
- data/lib/generators/openasn/install/install_generator.rb +68 -0
- data/lib/generators/openasn/install/templates/openasn.rb +68 -0
- data/lib/openasn/binary_format.rb +305 -0
- data/lib/openasn/cidr_utils.rb +49 -0
- data/lib/openasn/classifier.rb +152 -0
- data/lib/openasn/configuration.rb +163 -0
- data/lib/openasn/data/seed/fetch-manifest.json +589 -0
- data/lib/openasn/data/seed/manifest.json +99 -0
- data/lib/openasn/data/seed/openasn-ipv4.bin +0 -0
- data/lib/openasn/data/seed/openasn-ipv6.bin +0 -0
- data/lib/openasn/dataset.rb +117 -0
- data/lib/openasn/errors.rb +22 -0
- data/lib/openasn/http_client.rb +86 -0
- data/lib/openasn/ip.rb +70 -0
- data/lib/openasn/middleware.rb +49 -0
- data/lib/openasn/overlay_store.rb +124 -0
- data/lib/openasn/parsers.rb +515 -0
- data/lib/openasn/railtie.rb +30 -0
- data/lib/openasn/result.rb +93 -0
- data/lib/openasn/snapshot.rb +171 -0
- data/lib/openasn/special_ranges.rb +44 -0
- data/lib/openasn/tier_b.rb +223 -0
- data/lib/openasn/update_job.rb +32 -0
- data/lib/openasn/updater.rb +173 -0
- data/lib/openasn/version.rb +5 -0
- data/lib/openasn.rb +115 -0
- metadata +85 -0
data/lib/openasn.rb
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# stdlib only (zero-dependency contract). "time" is load-bearing: it
|
|
4
|
+
# provides Time.parse / Time.iso8601 / Time#iso8601 used across the
|
|
5
|
+
# updater, overlay store, and staleness probes — do not assume another
|
|
6
|
+
# library loaded it (bare non-Rails processes won't have it).
|
|
7
|
+
require "time"
|
|
8
|
+
|
|
9
|
+
require_relative "openasn/version"
|
|
10
|
+
require_relative "openasn/errors"
|
|
11
|
+
require_relative "openasn/ip"
|
|
12
|
+
require_relative "openasn/binary_format"
|
|
13
|
+
require_relative "openasn/special_ranges"
|
|
14
|
+
require_relative "openasn/configuration"
|
|
15
|
+
require_relative "openasn/result"
|
|
16
|
+
require_relative "openasn/overlay_store"
|
|
17
|
+
require_relative "openasn/snapshot"
|
|
18
|
+
require_relative "openasn/classifier"
|
|
19
|
+
require_relative "openasn/dataset"
|
|
20
|
+
require_relative "openasn/http_client"
|
|
21
|
+
require_relative "openasn/cidr_utils"
|
|
22
|
+
require_relative "openasn/parsers"
|
|
23
|
+
require_relative "openasn/tier_b"
|
|
24
|
+
require_relative "openasn/updater"
|
|
25
|
+
require_relative "openasn/middleware"
|
|
26
|
+
require_relative "openasn/railtie" if defined?(Rails::Railtie)
|
|
27
|
+
|
|
28
|
+
# OpenASN — offline IP origin intelligence.
|
|
29
|
+
#
|
|
30
|
+
# OpenASN.lookup("203.0.113.42")
|
|
31
|
+
# # => #<OpenASN::Result 203.0.113.42 verdict=unknown …>
|
|
32
|
+
#
|
|
33
|
+
# r = OpenASN.lookup(request.remote_ip)
|
|
34
|
+
# r.verdict # :residential_isp | :mobile | :business | :hosting |
|
|
35
|
+
# # :vpn | :tor_exit | :relay | :enterprise_gateway |
|
|
36
|
+
# # :education | :government | :cgnat | :private | :unknown
|
|
37
|
+
# r.infrastructure? # verdict in {hosting, vpn, tor_exit} — the honest boolean
|
|
38
|
+
# r.likely_human? # verdict in {residential_isp, mobile, relay, cgnat, enterprise_gateway}
|
|
39
|
+
# r.asn / r.as_org / r.category / r.network_role / r.provider / r.sources
|
|
40
|
+
#
|
|
41
|
+
# Every lookup is local (microseconds, no network). Data ships as a bundled
|
|
42
|
+
# seed and refreshes from OpenASN's nightly releases + Tier B authorities
|
|
43
|
+
# via OpenASN.update! / OpenASN::UpdateJob.
|
|
44
|
+
#
|
|
45
|
+
# REMEMBER WHAT VERDICTS MEAN: a clean/`residential_isp` verdict is absence
|
|
46
|
+
# of evidence, NOT proof of innocence — residential proxies are invisible
|
|
47
|
+
# to any offline dataset. `vpn`/`hosting`/`tor_exit` are high-confidence;
|
|
48
|
+
# treat everything else as a signal, not a sentence. Never hard-block
|
|
49
|
+
# `relay`, `cgnat`, or `mobile`: those are real people.
|
|
50
|
+
module OpenASN
|
|
51
|
+
class << self
|
|
52
|
+
def configuration
|
|
53
|
+
@configuration ||= Configuration.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configure
|
|
57
|
+
yield(configuration)
|
|
58
|
+
# Config affects how snapshots load (data_dir, memory_mode, tier_b…):
|
|
59
|
+
# drop any snapshot built under the old config. Cheap; lazy reload.
|
|
60
|
+
@dataset = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def dataset
|
|
64
|
+
@dataset ||= Dataset.new(configuration)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Classify an IP (String or IPAddr). Never returns nil; raises
|
|
68
|
+
# OpenASN::InvalidIPError (an ArgumentError) on unparseable input.
|
|
69
|
+
def lookup(ip)
|
|
70
|
+
Classifier.classify(dataset.snapshot, ip)
|
|
71
|
+
end
|
|
72
|
+
alias check lookup
|
|
73
|
+
alias [] lookup
|
|
74
|
+
|
|
75
|
+
# Refresh canonical artifacts + Tier B overlays now, atomically
|
|
76
|
+
# swapping the in-memory dataset on success.
|
|
77
|
+
# -> :updated | :tier_b_only | :unchanged | :locked
|
|
78
|
+
def update!(force: false)
|
|
79
|
+
Updater.new(configuration, dataset).run(force: force)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Load the dataset at boot instead of on first lookup (call from an
|
|
83
|
+
# initializer in latency-sensitive apps; first lazy load costs ~50-200ms
|
|
84
|
+
# depending on memory_mode).
|
|
85
|
+
def eager_load!
|
|
86
|
+
dataset.eager_load!
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dataset_info
|
|
90
|
+
dataset.info
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Cheap "is the on-disk data old?" probe that does NOT load the
|
|
94
|
+
# dataset. Considers the freshest of data_dir and the bundled seed
|
|
95
|
+
# (used by the Railtie's boot staleness check).
|
|
96
|
+
def data_stale_on_disk?(max_age: Dataset::STALE_AFTER)
|
|
97
|
+
build_ids = [File.join(configuration.data_dir, "manifest.json"),
|
|
98
|
+
File.join(Snapshot::SEED_DIR, "manifest.json")].filter_map do |path|
|
|
99
|
+
next unless File.exist?(path)
|
|
100
|
+
|
|
101
|
+
JSON.parse(File.read(path))["build_id"]
|
|
102
|
+
rescue JSON::ParserError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
newest = build_ids.filter_map { |id| Time.iso8601(id) rescue nil }.max # rubocop:disable Style/RescueModifier
|
|
106
|
+
newest.nil? || (Time.now - newest) > max_age
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Test/console helper: forget configuration and loaded data.
|
|
110
|
+
def reset!
|
|
111
|
+
@configuration = nil
|
|
112
|
+
@dataset = nil
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: openasn
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- rameerez
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-07-06 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'OpenASN classifies where an IP address is really coming from — residential
|
|
13
|
+
ISP, mobile carrier, hosting/datacenter, VPN, Tor exit, iCloud Private Relay, enterprise
|
|
14
|
+
gateway, business, education, government, CGNAT, or unknown — entirely offline,
|
|
15
|
+
in microseconds, with zero runtime dependencies and zero API calls. It bundles a
|
|
16
|
+
seed of the open OpenASN dataset (CC0), refreshes it nightly from GitHub Releases,
|
|
17
|
+
and layers fast-moving overlays (Tor exits, cloud ranges, Apple Private Relay) fetched
|
|
18
|
+
by your own server from the original authorities. Verdict-first API with full explainability:
|
|
19
|
+
every classification is auditable to its source.'
|
|
20
|
+
email:
|
|
21
|
+
- rubygems@rameerez.com
|
|
22
|
+
executables: []
|
|
23
|
+
extensions: []
|
|
24
|
+
extra_rdoc_files: []
|
|
25
|
+
files:
|
|
26
|
+
- ".simplecov"
|
|
27
|
+
- AGENTS.md
|
|
28
|
+
- CHANGELOG.md
|
|
29
|
+
- LICENSE.txt
|
|
30
|
+
- README.md
|
|
31
|
+
- Rakefile
|
|
32
|
+
- lib/generators/openasn/install/install_generator.rb
|
|
33
|
+
- lib/generators/openasn/install/templates/openasn.rb
|
|
34
|
+
- lib/openasn.rb
|
|
35
|
+
- lib/openasn/binary_format.rb
|
|
36
|
+
- lib/openasn/cidr_utils.rb
|
|
37
|
+
- lib/openasn/classifier.rb
|
|
38
|
+
- lib/openasn/configuration.rb
|
|
39
|
+
- lib/openasn/data/seed/fetch-manifest.json
|
|
40
|
+
- lib/openasn/data/seed/manifest.json
|
|
41
|
+
- lib/openasn/data/seed/openasn-ipv4.bin
|
|
42
|
+
- lib/openasn/data/seed/openasn-ipv6.bin
|
|
43
|
+
- lib/openasn/dataset.rb
|
|
44
|
+
- lib/openasn/errors.rb
|
|
45
|
+
- lib/openasn/http_client.rb
|
|
46
|
+
- lib/openasn/ip.rb
|
|
47
|
+
- lib/openasn/middleware.rb
|
|
48
|
+
- lib/openasn/overlay_store.rb
|
|
49
|
+
- lib/openasn/parsers.rb
|
|
50
|
+
- lib/openasn/railtie.rb
|
|
51
|
+
- lib/openasn/result.rb
|
|
52
|
+
- lib/openasn/snapshot.rb
|
|
53
|
+
- lib/openasn/special_ranges.rb
|
|
54
|
+
- lib/openasn/tier_b.rb
|
|
55
|
+
- lib/openasn/update_job.rb
|
|
56
|
+
- lib/openasn/updater.rb
|
|
57
|
+
- lib/openasn/version.rb
|
|
58
|
+
homepage: https://github.com/openasn/openasn-ruby
|
|
59
|
+
licenses:
|
|
60
|
+
- MIT
|
|
61
|
+
metadata:
|
|
62
|
+
allowed_push_host: https://rubygems.org
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
homepage_uri: https://github.com/openasn/openasn-ruby
|
|
65
|
+
source_code_uri: https://github.com/openasn/openasn-ruby
|
|
66
|
+
changelog_uri: https://github.com/openasn/openasn-ruby/blob/main/CHANGELOG.md
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 3.1.0
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.6.2
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: 'Offline IP origin intelligence: classify IPs as residential, mobile, hosting,
|
|
84
|
+
VPN, Tor, relay — zero API calls'
|
|
85
|
+
test_files: []
|