Class: OpenASN::Result

Inherits:
Object
  • Object
show all
Defined in:
lib/openasn/result.rb

Overview

The answer to "where is this IP really coming from?". Immutable.

Verdict-first design: verdict is the closed enum; the predicates are sugar. There is deliberately NO suspicious? — that's a policy word, and drawing that line belongs to your application, not this gem.

STABILITY CONTRACT (README "API stability contract" is the canonical text; keep in sync): VERDICTS is append-only — never remove, rename, or redefine an entry; additions land in minor versions with a loud CHANGELOG note. Verdicts are compiled code, never data: a data refresh cannot introduce one. to_h keys are append-only. The same contract binds every future client (openasn-js, …) — the enum is the project's cross-language API, defined in the data repo's DECISIONS.md.

Constant Summary collapse

VERDICTS =
%i[
  residential_isp mobile business hosting vpn tor_exit relay
  enterprise_gateway education government cgnat private unknown
].freeze
INFRASTRUCTURE_VERDICTS =

High-confidence "this is infrastructure, not an eyeball connection".

%i[hosting vpn tor_exit].freeze
LIKELY_HUMAN_VERDICTS =

"Very likely a human being on the other end" — including the classes people wrongly block: relay users are paying iCloud+ customers, CGNAT/mobile IPs are hundreds of people each, enterprise gateways are entire offices. Note the deliberate asymmetry: business/education/ government/unknown are NEITHER infrastructure nor likely_human — your app decides those.

%i[residential_isp mobile relay cgnat enterprise_gateway].freeze
LABELS =

Short human-readable names for every verdict — what you'd print in an admin table or a log line a human reads. Deliberately neutral wording (no "suspicious", no "risky"): the label states what the network IS; what to do about it is your app's policy. Keys follow the VERDICTS append-only contract.

{
  residential_isp: "Residential ISP",
  mobile: "Mobile carrier",
  business: "Business",
  hosting: "Hosting / datacenter",
  vpn: "VPN",
  tor_exit: "Tor exit",
  relay: "Privacy relay",
  enterprise_gateway: "Corporate gateway",
  education: "University / research",
  government: "Government",
  cgnat: "Carrier NAT",
  private: "Private IP",
  unknown: "Unknown"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ip:, verdict:, asn: nil, as_org: nil, category: nil, network_role: nil, provider: nil, sources: [], flags: 0, context_flags: [], unrouted: false) ⇒ Result

Returns a new instance of Result.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/openasn/result.rb', line 60

def initialize(ip:, verdict:, asn: nil, as_org: nil, category: nil,
               network_role: nil, provider: nil, sources: [], flags: 0,
               context_flags: [], unrouted: false)
  @ip = ip
  @verdict = verdict
  @asn = asn
  @as_org = as_org
  @category = category
  @network_role = network_role
  @provider = provider
  @sources = sources.freeze
  @flags = flags
  # The raw u16 is a wire detail; apps want names. Decoded once here so
  # nobody downstream needs BinaryFormat bit knowledge.
  @flag_names = BinaryFormat.flag_names(flags).freeze
  @context_flags = context_flags.freeze
  @unrouted = unrouted
  freeze
end

Instance Attribute Details

#as_orgObject (readonly)

Returns the value of attribute as_org.



57
58
59
# File 'lib/openasn/result.rb', line 57

def as_org
  @as_org
end

#asnObject (readonly)

Returns the value of attribute asn.



57
58
59
# File 'lib/openasn/result.rb', line 57

def asn
  @asn
end

#categoryObject (readonly)

Returns the value of attribute category.



57
58
59
# File 'lib/openasn/result.rb', line 57

def category
  @category
end

#context_flagsObject (readonly)

Returns the value of attribute context_flags.



57
58
59
# File 'lib/openasn/result.rb', line 57

def context_flags
  @context_flags
end

#flag_namesObject (readonly)

Returns the value of attribute flag_names.



57
58
59
# File 'lib/openasn/result.rb', line 57

def flag_names
  @flag_names
end

#flagsObject (readonly)

Returns the value of attribute flags.



57
58
59
# File 'lib/openasn/result.rb', line 57

def flags
  @flags
end

#ipObject (readonly)

Returns the value of attribute ip.



57
58
59
# File 'lib/openasn/result.rb', line 57

def ip
  @ip
end

#network_roleObject (readonly)

Returns the value of attribute network_role.



57
58
59
# File 'lib/openasn/result.rb', line 57

def network_role
  @network_role
end

#providerObject (readonly)

Returns the value of attribute provider.



57
58
59
# File 'lib/openasn/result.rb', line 57

def provider
  @provider
end

#sourcesObject (readonly)

Returns the value of attribute sources.



57
58
59
# File 'lib/openasn/result.rb', line 57

def sources
  @sources
end

#verdictObject (readonly)

Returns the value of attribute verdict.



57
58
59
# File 'lib/openasn/result.rb', line 57

def verdict
  @verdict
end

Instance Method Details

#bad_asn?Boolean

The ASN appears in brianhama/bad-asn-list — a curated catalog of hosting/cloud/colo ASNs. An infrastructure signal worth an admin's glance, NOT a verdict override and NOT a claim of abuse.

Returns:

  • (Boolean)


94
# File 'lib/openasn/result.rb', line 94

def bad_asn? = flag?(:bad_asn)

#cgnat?Boolean

Returns:

  • (Boolean)


102
# File 'lib/openasn/result.rb', line 102

def cgnat?   = verdict == :cgnat

#flag?(name) ⇒ Boolean

Named ASN-level flag check: flag?(:bad_asn), flag?(:cdn), … (the full vocabulary is BinaryFormat::FLAG_NAMES.values / the README table).

Returns:

  • (Boolean)


89
# File 'lib/openasn/result.rb', line 89

def flag?(name) = flag_names.include?(name)

#hosting?Boolean

Returns:

  • (Boolean)


97
# File 'lib/openasn/result.rb', line 97

def hosting? = verdict == :hosting

#infrastructure?Boolean

Returns:

  • (Boolean)


84
# File 'lib/openasn/result.rb', line 84

def infrastructure? = INFRASTRUCTURE_VERDICTS.include?(verdict)

#inspectObject Also known as: to_s



129
130
131
# File 'lib/openasn/result.rb', line 129

def inspect
  "#<OpenASN::Result #{ip} verdict=#{verdict}#{asn ? " AS#{asn}" : ''}#{as_org ? " (#{as_org})" : ''} sources=#{sources.inspect}>"
end

#labelObject

The verdict as a short human-readable string ("Residential ISP", "Hosting / datacenter") — for admin tables, tooltips, log lines.



82
# File 'lib/openasn/result.rb', line 82

def label = LABELS.fetch(verdict)

#likely_human?Boolean

Returns:

  • (Boolean)


85
# File 'lib/openasn/result.rb', line 85

def likely_human?   = LIKELY_HUMAN_VERDICTS.include?(verdict)

#mobile?Boolean

Returns:

  • (Boolean)


100
# File 'lib/openasn/result.rb', line 100

def mobile?  = verdict == :mobile

#private?Boolean

Returns:

  • (Boolean)


101
# File 'lib/openasn/result.rb', line 101

def private? = verdict == :private

#relay?Boolean

Returns:

  • (Boolean)


99
# File 'lib/openasn/result.rb', line 99

def relay?   = verdict == :relay

#to_hObject

Everything, for logging and shadow mode. Stable keys — CarHey-style shadow analyses depend on this shape staying append-only. (flag_names added in 0.3.0: the raw bitfield is useless in a log line.)



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/openasn/result.rb', line 110

def to_h
  {
    ip: ip,
    verdict: verdict,
    infrastructure: infrastructure?,
    likely_human: likely_human?,
    asn: asn,
    as_org: as_org,
    category: category,
    network_role: network_role,
    provider: provider,
    sources: sources,
    flags: flags,
    flag_names: flag_names,
    context_flags: context_flags,
    unrouted: unrouted?
  }
end

#tor?Boolean

Returns:

  • (Boolean)


98
# File 'lib/openasn/result.rb', line 98

def tor?     = verdict == :tor_exit

#unrouted?Boolean

True when no ASN announces this IP (unallocated/unrouted space).

Returns:

  • (Boolean)


105
# File 'lib/openasn/result.rb', line 105

def unrouted? = @unrouted

#vpn?Boolean

Returns:

  • (Boolean)


96
# File 'lib/openasn/result.rb', line 96

def vpn?     = verdict == :vpn