maxmind-geoip2 0.3.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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE-APACHE +202 -0
  6. data/LICENSE-MIT +17 -0
  7. data/README.dev.md +4 -0
  8. data/README.md +326 -0
  9. data/Rakefile +14 -0
  10. data/lib/maxmind/geoip2.rb +4 -0
  11. data/lib/maxmind/geoip2/client.rb +315 -0
  12. data/lib/maxmind/geoip2/errors.rb +41 -0
  13. data/lib/maxmind/geoip2/model/abstract.rb +31 -0
  14. data/lib/maxmind/geoip2/model/anonymous_ip.rb +67 -0
  15. data/lib/maxmind/geoip2/model/asn.rb +43 -0
  16. data/lib/maxmind/geoip2/model/city.rb +79 -0
  17. data/lib/maxmind/geoip2/model/connection_type.rb +36 -0
  18. data/lib/maxmind/geoip2/model/country.rb +74 -0
  19. data/lib/maxmind/geoip2/model/domain.rb +36 -0
  20. data/lib/maxmind/geoip2/model/enterprise.rb +19 -0
  21. data/lib/maxmind/geoip2/model/insights.rb +18 -0
  22. data/lib/maxmind/geoip2/model/isp.rb +57 -0
  23. data/lib/maxmind/geoip2/reader.rb +279 -0
  24. data/lib/maxmind/geoip2/record/abstract.rb +26 -0
  25. data/lib/maxmind/geoip2/record/city.rb +42 -0
  26. data/lib/maxmind/geoip2/record/continent.rb +41 -0
  27. data/lib/maxmind/geoip2/record/country.rb +58 -0
  28. data/lib/maxmind/geoip2/record/location.rb +77 -0
  29. data/lib/maxmind/geoip2/record/maxmind.rb +21 -0
  30. data/lib/maxmind/geoip2/record/place.rb +32 -0
  31. data/lib/maxmind/geoip2/record/postal.rb +34 -0
  32. data/lib/maxmind/geoip2/record/represented_country.rb +27 -0
  33. data/lib/maxmind/geoip2/record/subdivision.rb +52 -0
  34. data/lib/maxmind/geoip2/record/traits.rb +204 -0
  35. data/maxmind-geoip2.gemspec +25 -0
  36. data/test/data/LICENSE +4 -0
  37. data/test/data/MaxMind-DB-spec.md +570 -0
  38. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  39. data/test/data/README.md +4 -0
  40. data/test/data/bad-data/README.md +7 -0
  41. data/test/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
  42. data/test/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
  43. data/test/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +1 -0
  44. data/test/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
  45. data/test/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
  46. data/test/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +1 -0
  47. data/test/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +1 -0
  48. data/test/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
  49. data/test/data/perltidyrc +12 -0
  50. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +48 -0
  51. data/test/data/source-data/GeoIP2-City-Test.json +12852 -0
  52. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +102 -0
  53. data/test/data/source-data/GeoIP2-Country-Test.json +15916 -0
  54. data/test/data/source-data/GeoIP2-DensityIncome-Test.json +14 -0
  55. data/test/data/source-data/GeoIP2-Domain-Test.json +452 -0
  56. data/test/data/source-data/GeoIP2-Enterprise-Test.json +687 -0
  57. data/test/data/source-data/GeoIP2-ISP-Test.json +12593 -0
  58. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +2061 -0
  59. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +2132 -0
  60. data/test/data/source-data/GeoIP2-User-Count-Test.json +2837 -0
  61. data/test/data/source-data/GeoLite2-ASN-Test.json +37 -0
  62. data/test/data/source-data/README +15 -0
  63. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  64. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  65. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  66. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  67. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  68. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  69. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  70. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  71. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  72. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  73. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  74. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  75. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  76. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  77. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  78. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  79. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  80. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  81. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  82. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  83. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  84. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  85. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  86. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  87. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  88. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  89. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  90. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  91. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  92. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  93. data/test/data/test-data/README.md +26 -0
  94. data/test/data/test-data/maps-with-pointers.raw +0 -0
  95. data/test/data/test-data/write-test-data.pl +641 -0
  96. data/test/data/tidyall.ini +5 -0
  97. data/test/test_client.rb +424 -0
  98. data/test/test_model_country.rb +80 -0
  99. data/test/test_model_names.rb +47 -0
  100. data/test/test_reader.rb +459 -0
  101. metadata +177 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/testtask'
4
+ require 'rubocop/rake_task'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ end
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ desc 'Run tests and RuboCop'
13
+ task default: :test
14
+ task default: :rubocop
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'maxmind/geoip2/client'
4
+ require 'maxmind/geoip2/reader'
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'http'
4
+ require 'json'
5
+ require 'maxmind/geoip2/errors'
6
+ require 'maxmind/geoip2/model/city'
7
+ require 'maxmind/geoip2/model/country'
8
+ require 'maxmind/geoip2/model/insights'
9
+
10
+ module MaxMind
11
+ module GeoIP2
12
+ # This class provides a client API for all the
13
+ # {https://dev.maxmind.com/geoip/geoip2/web-services/ GeoIP2 Precision web
14
+ # services}. The services are Country, City, and Insights. Each service
15
+ # returns a different set of data about an IP address, with Country returning
16
+ # the least data and Insights the most.
17
+ #
18
+ # Each web service is represented by a different model class, and these model
19
+ # classes in turn contain multiple record classes. The record classes have
20
+ # attributes which contain data about the IP address.
21
+ #
22
+ # If the web service does not return a particular piece of data for an IP
23
+ # address, the associated attribute is not populated.
24
+ #
25
+ # The web service may not return any information for an entire record, in
26
+ # which case all of the attributes for that record class will be empty.
27
+ #
28
+ # == Usage
29
+ #
30
+ # The basic API for this class is the same for all of the web service end
31
+ # points. First you create a web service client object with your MaxMind
32
+ # account ID and license key, then you call the method corresponding to a
33
+ # specific end point, passing it the IP address you want to look up.
34
+ #
35
+ # If the request succeeds, the method call will return a model class for the
36
+ # service you called. This model in turn contains multiple record classes,
37
+ # each of which represents part of the data returned by the web service.
38
+ #
39
+ # If the request fails, the client class throws an exception.
40
+ #
41
+ # == Example
42
+ #
43
+ # require 'maxmind/geoip2'
44
+ #
45
+ # client = MaxMind::GeoIP2::Client.new(
46
+ # account_id: 42,
47
+ # license_key: 'abcdef123456',
48
+ # )
49
+ #
50
+ # # Replace 'city' with the method corresponding to the web service you
51
+ # # are using, e.g., 'country', 'insights'.
52
+ # record = client.city('128.101.101.101')
53
+ #
54
+ # puts record.country.iso_code
55
+ class Client
56
+ # rubocop:disable Metrics/ParameterLists
57
+
58
+ # Create a Client that may be used to query a GeoIP2 Precision web service.
59
+ #
60
+ # Once created, the Client is safe to use for lookups from multiple
61
+ # threads.
62
+ #
63
+ # @param account_id [Integer] your MaxMind account ID.
64
+ #
65
+ # @param license_key [String] your MaxMind license key.
66
+ #
67
+ # @param locales [Array<String>] a list of locale codes to use in the name
68
+ # property from most preferred to least preferred.
69
+ #
70
+ # @param host [String] the host to use when querying the web service.
71
+ #
72
+ # @param timeout [Integer] the number of seconds to wait for a request
73
+ # before timing out. If 0, no timeout is set.
74
+ #
75
+ # @param proxy_address [String] proxy address to use, if any.
76
+ #
77
+ # @param proxy_port [Integer] proxy port to use, if any.
78
+ #
79
+ # @param proxy_username [String] proxy username to use, if any.
80
+ #
81
+ # @param proxy_password [String] proxy password to use, if any.
82
+ def initialize(
83
+ account_id:,
84
+ license_key:,
85
+ locales: ['en'],
86
+ host: 'geoip.maxmind.com',
87
+ timeout: 0,
88
+ proxy_address: '',
89
+ proxy_port: 0,
90
+ proxy_username: '',
91
+ proxy_password: ''
92
+ )
93
+ @account_id = account_id
94
+ @license_key = license_key
95
+ @locales = locales
96
+ @host = host
97
+ @timeout = timeout
98
+ @proxy_address = proxy_address
99
+ @proxy_port = proxy_port
100
+ @proxy_username = proxy_username
101
+ @proxy_password = proxy_password
102
+ end
103
+ # rubocop:enable Metrics/ParameterLists
104
+
105
+ # This method calls the GeoIP2 Precision City web service.
106
+ #
107
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
108
+ # address is provided, the address that the web service is called from is
109
+ # used.
110
+ #
111
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
112
+ # such as an error connecting.
113
+ #
114
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
115
+ #
116
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
117
+ # an unexpected HTTP status code.
118
+ #
119
+ # @raise [AddressInvalidError] if the web service believes the IP address
120
+ # to be invalid or missing.
121
+ #
122
+ # @raise [AddressNotFoundError] if the IP address was not found.
123
+ #
124
+ # @raise [AddressReservedError] if the IP address is reserved.
125
+ #
126
+ # @raise [AuthenticationError] if there was a problem authenticating to the
127
+ # web service, such as an invalid or missing license key.
128
+ #
129
+ # @raise [InsufficientFundsError] if your account is out of credit.
130
+ #
131
+ # @raise [PermissionRequiredError] if your account does not have permission
132
+ # to use the web service.
133
+ #
134
+ # @raise [InvalidRequestError] if the web service responded with an error
135
+ # and there is no more specific error to raise.
136
+ #
137
+ # @return [MaxMind::GeoIP2::Model::City]
138
+ def city(ip_address = 'me')
139
+ response_for('city', MaxMind::GeoIP2::Model::City, ip_address)
140
+ end
141
+
142
+ # This method calls the GeoIP2 Precision Country web service.
143
+ #
144
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
145
+ # address is provided, the address that the web service is called from is
146
+ # used.
147
+ #
148
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
149
+ # such as an error connecting.
150
+ #
151
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
152
+ #
153
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
154
+ # an unexpected HTTP status code.
155
+ #
156
+ # @raise [AddressInvalidError] if the web service believes the IP address
157
+ # to be invalid or missing.
158
+ #
159
+ # @raise [AddressNotFoundError] if the IP address was not found.
160
+ #
161
+ # @raise [AddressReservedError] if the IP address is reserved.
162
+ #
163
+ # @raise [AuthenticationError] if there was a problem authenticating to the
164
+ # web service, such as an invalid or missing license key.
165
+ #
166
+ # @raise [InsufficientFundsError] if your account is out of credit.
167
+ #
168
+ # @raise [PermissionRequiredError] if your account does not have permission
169
+ # to use the web service.
170
+ #
171
+ # @raise [InvalidRequestError] if the web service responded with an error
172
+ # and there is no more specific error to raise.
173
+ #
174
+ # @return [MaxMind::GeoIP2::Model::Country]
175
+ def country(ip_address = 'me')
176
+ response_for('country', MaxMind::GeoIP2::Model::Country, ip_address)
177
+ end
178
+
179
+ # This method calls the GeoIP2 Precision Insights web service.
180
+ #
181
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
182
+ # address is provided, the address that the web service is called from is
183
+ # used.
184
+ #
185
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
186
+ # such as an error connecting.
187
+ #
188
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
189
+ #
190
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
191
+ # an unexpected HTTP status code.
192
+ #
193
+ # @raise [AddressInvalidError] if the web service believes the IP address
194
+ # to be invalid or missing.
195
+ #
196
+ # @raise [AddressNotFoundError] if the IP address was not found.
197
+ #
198
+ # @raise [AddressReservedError] if the IP address is reserved.
199
+ #
200
+ # @raise [AuthenticationError] if there was a problem authenticating to the
201
+ # web service, such as an invalid or missing license key.
202
+ #
203
+ # @raise [InsufficientFundsError] if your account is out of credit.
204
+ #
205
+ # @raise [PermissionRequiredError] if your account does not have permission
206
+ # to use the web service.
207
+ #
208
+ # @raise [InvalidRequestError] if the web service responded with an error
209
+ # and there is no more specific error to raise.
210
+ #
211
+ # @return [MaxMind::GeoIP2::Model::Insights]
212
+ def insights(ip_address = 'me')
213
+ response_for('insights', MaxMind::GeoIP2::Model::Insights, ip_address)
214
+ end
215
+
216
+ private
217
+
218
+ def response_for(endpoint, model_class, ip_address)
219
+ record = get(endpoint, ip_address)
220
+
221
+ model_class.new(record, @locales)
222
+ end
223
+
224
+ # rubocop:disable Metrics/CyclomaticComplexity
225
+ # rubocop:disable Metrics/PerceivedComplexity
226
+ def get(endpoint, ip_address)
227
+ url = 'https://' + @host + '/geoip/v2.1/' + endpoint + '/' + ip_address
228
+
229
+ headers = HTTP.basic_auth(user: @account_id, pass: @license_key)
230
+ .headers(
231
+ accept: 'application/json',
232
+ user_agent: 'MaxMind-GeoIP2-ruby',
233
+ )
234
+ timeout = @timeout > 0 ? headers.timeout(@timeout) : headers
235
+
236
+ proxy = timeout
237
+ if @proxy_address != ''
238
+ opts = {}
239
+ opts[:proxy_port] = @proxy_port if @proxy_port != 0
240
+ opts[:proxy_username] = @proxy_username if @proxy_username != ''
241
+ opts[:proxy_password] = @proxy_password if @proxy_password != ''
242
+ proxy = timeout.via(@proxy_address, opts)
243
+ end
244
+
245
+ response = proxy.get(url)
246
+
247
+ body = response.to_s
248
+ is_json = response.headers[:content_type]&.include?('json')
249
+
250
+ if response.status.client_error?
251
+ return handle_client_error(endpoint, response.code, body, is_json)
252
+ end
253
+
254
+ if response.status.server_error?
255
+ raise HTTPError,
256
+ "Received server error response (#{response.code}) for #{endpoint} with body #{body}"
257
+ end
258
+
259
+ if response.code != 200
260
+ raise HTTPError,
261
+ "Received unexpected response (#{response.code}) for #{endpoint} with body #{body}"
262
+ end
263
+
264
+ handle_success(endpoint, body, is_json)
265
+ end
266
+ # rubocop:enable Metrics/CyclomaticComplexity
267
+ # rubocop:enable Metrics/PerceivedComplexity
268
+
269
+ # rubocop:disable Metrics/CyclomaticComplexity
270
+ def handle_client_error(endpoint, status, body, is_json)
271
+ if !is_json
272
+ raise HTTPError,
273
+ "Received client error response (#{status}) for #{endpoint} but it is not JSON: #{body}"
274
+ end
275
+
276
+ error = JSON.parse(body)
277
+
278
+ if !error.key?('code') || !error.key?('error')
279
+ raise HTTPError,
280
+ "Received client error response (#{status}) that is JSON but does not specify code or error keys: #{body}"
281
+ end
282
+
283
+ case error['code']
284
+ when 'IP_ADDRESS_INVALID', 'IP_ADDRESS_REQUIRED'
285
+ raise AddressInvalidError, error['error']
286
+ when 'IP_ADDRESS_NOT_FOUND'
287
+ raise AddressNotFoundError, error['error']
288
+ when 'IP_ADDRESS_RESERVED'
289
+ raise AddressReservedError, error['error']
290
+ when 'ACCOUNT_ID_REQUIRED',
291
+ 'ACCOUNT_ID_UNKNOWN',
292
+ 'AUTHORIZATION_INVALID',
293
+ 'LICENSE_KEY_REQUIRED'
294
+ raise AuthenticationError, error['error']
295
+ when 'INSUFFICIENT_FUNDS'
296
+ raise InsufficientFundsError, error['error']
297
+ when 'PERMISSION_REQUIRED'
298
+ raise PermissionRequiredError, error['error']
299
+ else
300
+ raise InvalidRequestError, error['error']
301
+ end
302
+ end
303
+ # rubocop:enable Metrics/CyclomaticComplexity
304
+
305
+ def handle_success(endpoint, body, is_json)
306
+ if !is_json
307
+ raise HTTPError,
308
+ "Received a success response for #{endpoint} but it is not JSON: #{body}"
309
+ end
310
+
311
+ JSON.parse(body)
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaxMind
4
+ module GeoIP2
5
+ # An AddressNotFoundError means the IP address was not found in the
6
+ # database or the web service said the IP address was not found.
7
+ class AddressNotFoundError < RuntimeError
8
+ end
9
+
10
+ # An HTTPError means there was an unexpected HTTP status or response.
11
+ class HTTPError < RuntimeError
12
+ end
13
+
14
+ # An AddressInvalidError means the IP address was invalid.
15
+ class AddressInvalidError < RuntimeError
16
+ end
17
+
18
+ # An AddressReservedError means the IP address is reserved.
19
+ class AddressReservedError < RuntimeError
20
+ end
21
+
22
+ # An AuthenticationError means there was a problem authenticating to the
23
+ # web service.
24
+ class AuthenticationError < RuntimeError
25
+ end
26
+
27
+ # An InsufficientFundsError means the account is out of credits.
28
+ class InsufficientFundsError < RuntimeError
29
+ end
30
+
31
+ # A PermissionRequiredError means the account does not have permission to
32
+ # use the requested service.
33
+ class PermissionRequiredError < RuntimeError
34
+ end
35
+
36
+ # An InvalidRequestError means the web service returned an error and there
37
+ # is no more specific error class.
38
+ class InvalidRequestError < RuntimeError
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+
5
+ module MaxMind
6
+ module GeoIP2
7
+ module Model
8
+ # @!visibility private
9
+ class Abstract
10
+ def initialize(record)
11
+ @record = record
12
+
13
+ ip = IPAddr.new(record['ip_address']).mask(record['prefix_length'])
14
+ record['network'] = format('%s/%d', ip.to_s, record['prefix_length'])
15
+ end
16
+
17
+ protected
18
+
19
+ def get(key)
20
+ if @record.nil? || !@record.key?(key)
21
+ return false if key.start_with?('is_')
22
+
23
+ return nil
24
+ end
25
+
26
+ @record[key]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'maxmind/geoip2/model/abstract'
4
+
5
+ module MaxMind
6
+ module GeoIP2
7
+ module Model
8
+ # Model class for the Anonymous IP database.
9
+ class AnonymousIP < Abstract
10
+ # This is true if the IP address belongs to any sort of anonymous network.
11
+ #
12
+ # @return [Boolean]
13
+ def anonymous?
14
+ get('is_anonymous')
15
+ end
16
+
17
+ # This is true if the IP address is registered to an anonymous VPN
18
+ # provider. If a VPN provider does not register subnets under names
19
+ # associated with them, we will likely only flag their IP ranges using the
20
+ # hosting_provider? method.
21
+ #
22
+ # @return [Boolean]
23
+ def anonymous_vpn?
24
+ get('is_anonymous_vpn')
25
+ end
26
+
27
+ # This is true if the IP address belongs to a hosting or VPN provider (see
28
+ # description of the anonymous_vpn? method).
29
+ #
30
+ # @return [Boolean]
31
+ def hosting_provider?
32
+ get('is_hosting_provider')
33
+ end
34
+
35
+ # The IP address that the data in the model is for.
36
+ #
37
+ # @return [String]
38
+ def ip_address
39
+ get('ip_address')
40
+ end
41
+
42
+ # The network in CIDR notation associated with the record. In particular,
43
+ # this is the largest network where all of the fields besides ip_address
44
+ # have the same value.
45
+ #
46
+ # @return [String]
47
+ def network
48
+ get('network')
49
+ end
50
+
51
+ # This is true if the IP address belongs to a public proxy.
52
+ #
53
+ # @return [Boolean]
54
+ def public_proxy?
55
+ get('is_public_proxy')
56
+ end
57
+
58
+ # This is true if the IP address is a Tor exit node.
59
+ #
60
+ # @return [Boolean]
61
+ def tor_exit_node?
62
+ get('is_tor_exit_node')
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end