Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require: rubocop-performance
plugins:
- rubocop-performance

AllCops:
TargetRubyVersion: '3.0'
Expand Down Expand Up @@ -40,4 +41,4 @@ Style/HashTransformKeys:
Enabled: true

Gemspec/DevelopmentDependencies:
Enabled: false
Enabled: false
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 1.3.0

* Support for the GeoIP Anonymous Plus database has been added. To do a
lookup in this database, use the `anonymous_plus` method on
`MaxMind::GeoIP2::Reader`.
* Ruby 3.0+ is now required. If you're using Ruby 2.5, 2.6, or 2.7, please
use version 1.2.0 of this gem.
* Deprecated `metro_code` on `MaxMind::GeoIP2::Record::Location`. The code
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ record = reader.anonymous_ip('128.101.101.101')
puts "Anonymous" if record.is_anonymous
```

### Anonymous Plus Example

```ruby
require 'maxmind/geoip2'

# This creates the Reader object which should be reused across lookups.
reader = MaxMind::GeoIP2::Reader.new(
database: '/usr/share/GeoIP/GeoIP-Anonymous-Plus.mmdb',
)

record = reader.anonymous_plus('128.101.101.101')

puts record.anonymizer_confidence # 30
```

### ASN Example

```ruby
Expand Down
46 changes: 46 additions & 0 deletions lib/maxmind/geoip2/model/anonymous_plus.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'date'
require 'maxmind/geoip2/model/anonymous_ip'

module MaxMind
module GeoIP2
module Model
# Model class for the Anonymous Plus database.
class AnonymousPlus < AnonymousIP
# A score ranging from 1 to 99 that is our percent confidence that the
# network is currently part of an actively used VPN service.
#
# @return [Integer, nil]
def anonymizer_confidence
get('anonymizer_confidence')
end

# The last day that the network was sighted in our analysis of
# anonymized networks. This value is parsed lazily.
#
# @return [Date, nil] A Date object representing the last seen date,
# or nil if the date is not available.
def network_last_seen
return @network_last_seen if defined?(@network_last_seen)

date_string = get('network_last_seen')

if !date_string
return nil
end

@network_last_seen = Date.parse(date_string)
end

# The name of the VPN provider (e.g., NordVPN, SurfShark, etc.)
# associated with the network.
#
# @return [String, nil]
def provider_name
get('provider_name')
end
end
end
end
end
27 changes: 26 additions & 1 deletion lib/maxmind/geoip2/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'maxmind/db'
require 'maxmind/geoip2/errors'
require 'maxmind/geoip2/model/anonymous_ip'
require 'maxmind/geoip2/model/anonymous_plus'
require 'maxmind/geoip2/model/asn'
require 'maxmind/geoip2/model/city'
require 'maxmind/geoip2/model/connection_type'
Expand Down Expand Up @@ -86,7 +87,7 @@ def initialize(*args)
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity

# Look up the IP address in the database.
# Look up the IP address in the Anonymous IP database.
#
# @param ip_address [String] a string in the standard notation. It may be
# IPv4 or IPv6.
Expand All @@ -110,6 +111,30 @@ def anonymous_ip(ip_address)
)
end

# Look up the IP address in the Anonymous Plus database.
#
# @param ip_address [String] a string in the standard notation. It may be
# IPv4 or IPv6.
#
# @return [MaxMind::GeoIP2::Model::AnonymousPlus]
#
# @raise [ArgumentError] if used against a non-Anonymous Plus database
# or if you attempt to look up an IPv6 address in an IPv4 only database.
#
# @raise [AddressNotFoundError] if the IP address is not found in the
# database.
#
# @raise [MaxMind::DB::InvalidDatabaseError] if the database appears
# corrupt.
def anonymous_plus(ip_address)
flat_model_for(
Model::AnonymousPlus,
'anonymous_plus',
'GeoIP-Anonymous-Plus',
ip_address,
)
end

# Look up the IP address in an ASN database.
#
# @param ip_address [String] a string in the standard notation. It may be
Expand Down
2 changes: 1 addition & 1 deletion test/data
Submodule data updated 57 files
+7 −6 .github/workflows/codeql-analysis.yml
+6 −2 .github/workflows/go.yml
+7 −3 .github/workflows/golangci-lint.yml
+32 −0 .github/workflows/zizmor.yml
+0 −708 .golangci.toml
+737 −0 .golangci.yml
+0 −11 .perltidyallrc
+0 −5 .tidyallrc
+0 −4 LICENSE
+202 −0 LICENSE-APACHE
+17 −0 LICENSE-MIT
+1 −2 MaxMind-DB-spec.md
+8 −1 README.md
+1 −1 go.mod
+0 −18 perltidyrc
+7 −5 pkg/writer/geoip2.go
+2 −1 pkg/writer/maxmind.go
+4 −1 pkg/writer/writer.go
+175 −0 source-data/GeoIP-Anonymous-Plus-Test.json
+6 −0 source-data/GeoIP2-Anonymous-IP-Test.json
+73 −7 source-data/GeoIP2-City-Test.json
+3 −28 source-data/GeoIP2-Country-Test.json
+27 −8 source-data/GeoIP2-Enterprise-Test.json
+31 −0 source-data/GeoIP2-IP-Risk-Test.json
+585 −11 source-data/GeoIP2-Precision-Enterprise-Test.json
+0 −3 source-data/GeoLite2-City-Test.json
+0 −3 source-data/GeoLite2-Country-Test.json
+ test-data/GeoIP-Anonymous-Plus-Test.mmdb
+ test-data/GeoIP2-Anonymous-IP-Test.mmdb
+ test-data/GeoIP2-City-Test.mmdb
+ test-data/GeoIP2-Connection-Type-Test.mmdb
+ test-data/GeoIP2-Country-Test.mmdb
+ test-data/GeoIP2-DensityIncome-Test.mmdb
+ test-data/GeoIP2-Domain-Test.mmdb
+ test-data/GeoIP2-Enterprise-Test.mmdb
+ test-data/GeoIP2-IP-Risk-Test.mmdb
+ test-data/GeoIP2-ISP-Test.mmdb
+ test-data/GeoIP2-Precision-Enterprise-Test.mmdb
+ test-data/GeoIP2-Static-IP-Score-Test.mmdb
+ test-data/GeoIP2-User-Count-Test.mmdb
+ test-data/GeoLite2-ASN-Test.mmdb
+ test-data/GeoLite2-City-Test.mmdb
+ test-data/GeoLite2-Country-Test.mmdb
+ test-data/MaxMind-DB-no-ipv4-search-tree.mmdb
+ test-data/MaxMind-DB-string-value-entries.mmdb
+ test-data/MaxMind-DB-test-decoder.mmdb
+ test-data/MaxMind-DB-test-ipv4-24.mmdb
+ test-data/MaxMind-DB-test-ipv4-28.mmdb
+ test-data/MaxMind-DB-test-ipv4-32.mmdb
+ test-data/MaxMind-DB-test-ipv6-24.mmdb
+ test-data/MaxMind-DB-test-ipv6-28.mmdb
+ test-data/MaxMind-DB-test-ipv6-32.mmdb
+ test-data/MaxMind-DB-test-metadata-pointers.mmdb
+ test-data/MaxMind-DB-test-mixed-24.mmdb
+ test-data/MaxMind-DB-test-mixed-28.mmdb
+ test-data/MaxMind-DB-test-mixed-32.mmdb
+ test-data/MaxMind-DB-test-nested.mmdb
23 changes: 23 additions & 0 deletions test/test_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ def test_anonymous_ip_residential_proxy
reader.close
end

def test_anonymous_plus
reader = MaxMind::GeoIP2::Reader.new(
'test/data/test-data/GeoIP-Anonymous-Plus-Test.mmdb',
)
ip = '1.2.0.1'
record = reader.anonymous_plus(ip)

assert_equal(30, record.anonymizer_confidence)
assert_equal(true, record.anonymous?)
assert_equal(true, record.anonymous_vpn?)
assert_equal(false, record.hosting_provider?)
assert_equal(Date.new(2025, 4, 14), record.network_last_seen)
assert_equal('foo', record.provider_name)
assert_equal(false, record.public_proxy?)
assert_equal(false, record.residential_proxy?)
assert_equal(false, record.tor_exit_node?)

assert_equal(ip, record.ip_address)
assert_equal('1.2.0.1/32', record.network)

reader.close
end

def test_asn
reader = MaxMind::GeoIP2::Reader.new(
'test/data/test-data/GeoLite2-ASN-Test.mmdb',
Expand Down