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.
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: []