geoip 1.4.0 → 1.5.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 +5 -13
- data/bin/geoip +32 -1
- data/geoip.gemspec +4 -3
- data/lib/geoip.rb +320 -127
- data/test/test_file.rb +17 -0
- data/test/test_geoip.rb +18 -11
- data/test/test_helper.rb +8 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NTIwNDVkMzA2NWI3YjNjODA2ODY5ZDkzZTY3MDI0NDljYjMwNTYxNw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8e46df24a662de22a8500c265c5eaaf2bfb0cb29
|
4
|
+
data.tar.gz: d90310294059804b56d80ac41554fa68826393a2
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NmVkNzY2YmVhOTc1ZTIyNWEyNDlhNjhjYWNhNmZhZTAyYWVhNWIyMWFlYzdm
|
11
|
-
MjAxNjY0NzI4ODkyZmE3MmNiMjIzMWY3YmQwNjc0ZTQxYzYxMTE=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ODBjOWM0ODA1NWUyNDRlYTQ0NmJkY2ViZTI3MGQwNjI5N2JiODRhZTY3YTIx
|
14
|
-
NzdkN2EzZGZkYWZkM2JlZDA0MmFiMTk4MzRmNTU3NTVmMWMxYmYyN2JmNTAx
|
15
|
-
YmUzYWZkMGE4OTE5NmFkOTM1YmVmMjAwMTczODQ2Yzg0ZTJkMzc=
|
6
|
+
metadata.gz: 5ad6a2221b51acf61c658412786484bd5e65ce2b56a4969fdb8954870be6a292360364867584844585c31fb8a04a597c7c30301d9a19d6f0ca125de9f65c00f3
|
7
|
+
data.tar.gz: dba6732195af06951a47b1d36eaadad3d58f5a98fbbd35bae54858fe05a2966ecb1a009c63706873b287b8983d9d5ca38bedce586c0b72ebaeae994c76ea1554
|
data/bin/geoip
CHANGED
@@ -9,7 +9,38 @@ data = '/usr/share/GeoIP/GeoIP.dat'
|
|
9
9
|
data = ARGV.shift if ARGV[0] =~ /\.dat\Z/
|
10
10
|
|
11
11
|
geoip = GeoIP.new data
|
12
|
-
req =
|
12
|
+
req = case geoip.databaseType
|
13
|
+
when GeoIP::Edition::CITY_REV1,
|
14
|
+
GeoIP::Edition::CITY_REV0,
|
15
|
+
GeoIP::Edition::CITY_REV1_V6
|
16
|
+
:city
|
17
|
+
when GeoIP::Edition::REGION_REV0,
|
18
|
+
GeoIP::Edition::REGION_REV1
|
19
|
+
:region
|
20
|
+
when GeoIP::Edition::NETSPEED, GeoIP::Edition::NETSPEED_REV1
|
21
|
+
:netspeed
|
22
|
+
when GeoIP::Edition::ISP, # All these looked up the same way
|
23
|
+
GeoIP::Edition::ORG,
|
24
|
+
GeoIP::Edition::ISP,
|
25
|
+
GeoIP::Edition::DOMAIN,
|
26
|
+
GeoIP::Edition::ACCURACYRADIUS,
|
27
|
+
GeoIP::Edition::NETSPEED,
|
28
|
+
GeoIP::Edition::USERTYPE,
|
29
|
+
GeoIP::Edition::USERTYPE_V6,
|
30
|
+
GeoIP::Edition::REGISTRAR,
|
31
|
+
GeoIP::Edition::LOCATIONA,
|
32
|
+
GeoIP::Edition::CITYCONF,
|
33
|
+
GeoIP::Edition::COUNTRYCONF,
|
34
|
+
GeoIP::Edition::REGIONCONF,
|
35
|
+
GeoIP::Edition::POSTALCONF
|
36
|
+
:isp
|
37
|
+
when GeoIP::Edition::ASNUM
|
38
|
+
:asn
|
39
|
+
when GeoIP::Edition::COUNTRY, GeoIP::Edition::PROXY, GeoIP::Edition::COUNTRY_V6
|
40
|
+
:country
|
41
|
+
else
|
42
|
+
:asn
|
43
|
+
end
|
13
44
|
|
14
45
|
if ARGV.size > 0
|
15
46
|
ARGV.each { |a| p geoip.send(req, a) }
|
data/geoip.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: geoip 1.
|
5
|
+
# stub: geoip 1.5.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "geoip"
|
9
|
-
s.version = "1.
|
9
|
+
s.version = "1.5.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Clifford Heath", "Roland Moriz"]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2015-03-10"
|
15
15
|
s.description = "GeoIP searches a GeoIP database for a given host or IP address, and\nreturns information about the country where the IP address is allocated,\nand the city, ISP and other information, if you have that database version."
|
16
16
|
s.email = ["clifford.heath@gmail.com", "rmoriz@gmail.com"]
|
17
17
|
s.executables = ["geoip"]
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
"test/csvORG2dat.py",
|
37
37
|
"test/organizations.csv",
|
38
38
|
"test/organizations.dat",
|
39
|
+
"test/test_file.rb",
|
39
40
|
"test/test_geoip.rb",
|
40
41
|
"test/test_helper.rb"
|
41
42
|
]
|
data/lib/geoip.rb
CHANGED
@@ -58,7 +58,7 @@ require 'yaml'
|
|
58
58
|
class GeoIP
|
59
59
|
|
60
60
|
# The GeoIP GEM version number
|
61
|
-
VERSION = "1.
|
61
|
+
VERSION = "1.5.0"
|
62
62
|
|
63
63
|
# The +data/+ directory for geoip
|
64
64
|
DATA_DIR = File.expand_path(File.join(File.dirname(__FILE__),'..','data','geoip'))
|
@@ -96,6 +96,55 @@ class GeoIP
|
|
96
96
|
GEOIP_NETSPEED_EDITION = 10
|
97
97
|
GEOIP_COUNTRY_EDITION_V6 = 12
|
98
98
|
GEOIP_CITY_EDITION_REV1_V6 = 30
|
99
|
+
GEOIP_NETSPEED_EDITION_REV1 = 32
|
100
|
+
|
101
|
+
# Editions list updated from the C API, August 2014:
|
102
|
+
module Edition
|
103
|
+
COUNTRY = 1
|
104
|
+
REGION_REV0 = 7
|
105
|
+
CITY_REV0 = 6
|
106
|
+
ORG = 5
|
107
|
+
ISP = 4
|
108
|
+
CITY_REV1 = 2
|
109
|
+
REGION_REV1 = 3
|
110
|
+
PROXY = 8
|
111
|
+
ASNUM = 9
|
112
|
+
NETSPEED = 10
|
113
|
+
DOMAIN = 11
|
114
|
+
COUNTRY_V6 = 12
|
115
|
+
LOCATIONA = 13
|
116
|
+
ACCURACYRADIUS = 14
|
117
|
+
CITYCONFIDENCE = 15 # unsupported
|
118
|
+
CITYCONFIDENCEDIST = 16 # unsupported
|
119
|
+
LARGE_COUNTRY = 17
|
120
|
+
LARGE_COUNTRY_V6 = 18
|
121
|
+
CITYCONFIDENCEDIST_ISP_ORG = 19 # unused, but gaps are not allowed
|
122
|
+
CCM_COUNTRY = 20 # unused, but gaps are not allowed
|
123
|
+
ASNUM_V6 = 21
|
124
|
+
ISP_V6 = 22
|
125
|
+
ORG_V6 = 23
|
126
|
+
DOMAIN_V6 = 24
|
127
|
+
LOCATIONA_V6 = 25
|
128
|
+
REGISTRAR = 26
|
129
|
+
REGISTRAR_V6 = 27
|
130
|
+
USERTYPE = 28
|
131
|
+
USERTYPE_V6 = 29
|
132
|
+
CITY_REV1_V6 = 30
|
133
|
+
CITY_REV0_V6 = 31
|
134
|
+
NETSPEED_REV1 = 32
|
135
|
+
NETSPEED_REV1_V6 = 33
|
136
|
+
COUNTRYCONF = 34
|
137
|
+
CITYCONF = 35
|
138
|
+
REGIONCONF = 36
|
139
|
+
POSTALCONF = 37
|
140
|
+
ACCURACYRADIUS_V6 = 38
|
141
|
+
end
|
142
|
+
|
143
|
+
# Numeric codes for NETSPEED (NETSPEED_REV1* is string-based):
|
144
|
+
GEOIP_UNKNOWN_SPEED = 0
|
145
|
+
GEOIP_DIALUP_SPEED = 1
|
146
|
+
GEOIP_CABLEDSL_SPEED = 2
|
147
|
+
GEOIP_CORPORATE_SPEED = 3
|
99
148
|
|
100
149
|
COUNTRY_BEGIN = 16776960 #:nodoc:
|
101
150
|
STATE_BEGIN_REV0 = 16700000 #:nodoc:
|
@@ -181,7 +230,7 @@ class GeoIP
|
|
181
230
|
@use_pread = IO.respond_to?(:pread) && !options[:preload]
|
182
231
|
|
183
232
|
@options = options
|
184
|
-
@database_type =
|
233
|
+
@database_type = Edition::COUNTRY
|
185
234
|
@record_length = STANDARD_RECORD_LENGTH
|
186
235
|
@file = File.open(filename, 'rb')
|
187
236
|
|
@@ -210,43 +259,49 @@ class GeoIP
|
|
210
259
|
# * The two-character continent code
|
211
260
|
#
|
212
261
|
def country(hostname)
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
return city(hostname)
|
217
|
-
end
|
262
|
+
case @database_type
|
263
|
+
when Edition::CITY_REV0, Edition::CITY_REV1, Edition::CITY_REV1_V6
|
264
|
+
city(hostname)
|
218
265
|
|
219
|
-
|
220
|
-
|
221
|
-
return region(hostname)
|
222
|
-
end
|
266
|
+
when Edition::REGION_REV0, Edition::REGION_REV1
|
267
|
+
region(hostname)
|
223
268
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
269
|
+
when Edition::NETSPEED, Edition::NETSPEED_REV1
|
270
|
+
netspeed(hostname)
|
271
|
+
|
272
|
+
when Edition::COUNTRY, Edition::PROXY, Edition::COUNTRY_V6
|
273
|
+
ip = lookup_ip(hostname)
|
274
|
+
if @ip_bits > 32
|
275
|
+
ipaddr = IPAddr.new ip
|
276
|
+
code = (seek_record(ipaddr.to_i) - COUNTRY_BEGIN)
|
277
|
+
else
|
278
|
+
# Convert numeric IP address to an integer
|
279
|
+
ipnum = iptonum(ip)
|
280
|
+
code = (seek_record(ipnum) - @database_segments[0])
|
281
|
+
end
|
282
|
+
read_country(code, hostname, ip)
|
234
283
|
else
|
235
|
-
throw "Invalid GeoIP database type, can't look up Country by IP"
|
284
|
+
throw "Invalid GeoIP database type #{@database_type}, can't look up Country by IP"
|
236
285
|
end
|
286
|
+
end
|
237
287
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
288
|
+
# Search a GeoIP Connection Type (Netspeed) database for the specified host,
|
289
|
+
# returning the speed code.
|
290
|
+
#
|
291
|
+
# +hostname+ is a String holding the host's DNS name or numeric IP address.
|
292
|
+
def netspeed(hostname)
|
293
|
+
unless (@database_type == Edition::NETSPEED ||
|
294
|
+
@database_type == Edition::NETSPEED_REV1)
|
295
|
+
throw "Invalid GeoIP database type #{@database_type}, can't look up Netspeed by IP"
|
296
|
+
end
|
297
|
+
# Convert numeric IP address to an integer
|
298
|
+
ip = lookup_ip(hostname)
|
299
|
+
ipnum = iptonum(ip)
|
300
|
+
offset = (seek_record(ipnum) - @database_segments[0])
|
301
|
+
read_netspeed(offset)
|
247
302
|
end
|
248
303
|
|
249
|
-
# Search the GeoIP database for the specified host,
|
304
|
+
# Search the GeoIP database for the specified host, returning region info.
|
250
305
|
#
|
251
306
|
# +hostname+ is a String holding the hosts's DNS name or numeric IP
|
252
307
|
# address.
|
@@ -262,14 +317,14 @@ class GeoIP
|
|
262
317
|
# * The timezone name, if known
|
263
318
|
#
|
264
319
|
def region(hostname)
|
265
|
-
if (@database_type ==
|
266
|
-
@database_type ==
|
267
|
-
@database_type ==
|
320
|
+
if (@database_type == Edition::CITY_REV0 ||
|
321
|
+
@database_type == Edition::CITY_REV1 ||
|
322
|
+
@database_type == Edition::CITY_REV1_V6)
|
268
323
|
return city(hostname)
|
269
324
|
end
|
270
325
|
|
271
|
-
if (@database_type ==
|
272
|
-
@database_type ==
|
326
|
+
if (@database_type == Edition::REGION_REV0 ||
|
327
|
+
@database_type == Edition::REGION_REV1)
|
273
328
|
ip = lookup_ip(hostname)
|
274
329
|
ipnum = iptonum(ip)
|
275
330
|
pos = seek_record(ipnum)
|
@@ -306,14 +361,14 @@ class GeoIP
|
|
306
361
|
def city(hostname)
|
307
362
|
ip = lookup_ip(hostname)
|
308
363
|
|
309
|
-
if (@database_type ==
|
310
|
-
@database_type ==
|
364
|
+
if (@database_type == Edition::CITY_REV0 ||
|
365
|
+
@database_type == Edition::CITY_REV1)
|
311
366
|
# Convert numeric IP address to an integer
|
312
367
|
ipnum = iptonum(ip)
|
313
368
|
pos = seek_record(ipnum)
|
314
|
-
elsif (@database_type ==
|
369
|
+
elsif (@database_type == Edition::CITY_REV1_V6)
|
315
370
|
ipaddr = IPAddr.new ip
|
316
|
-
pos =
|
371
|
+
pos = seek_record(ipaddr.to_i)
|
317
372
|
else
|
318
373
|
throw "Invalid GeoIP database type, can't look up City by IP"
|
319
374
|
end
|
@@ -328,7 +383,7 @@ class GeoIP
|
|
328
383
|
# you can test whatever IP range you think is causing problems,
|
329
384
|
# as I don't care to undertake an exhaustive search of the 32-bit space.
|
330
385
|
unless pos == @database_segments[0]
|
331
|
-
read_city(pos, hostname, ip)
|
386
|
+
read_city(pos-@database_segments[0], hostname, ip)
|
332
387
|
end
|
333
388
|
end
|
334
389
|
|
@@ -347,24 +402,34 @@ class GeoIP
|
|
347
402
|
# Convert numeric IP address to an integer
|
348
403
|
ipnum = iptonum(ip)
|
349
404
|
|
350
|
-
|
351
|
-
|
405
|
+
case @database_type
|
406
|
+
when Edition::ORG,
|
407
|
+
Edition::ISP,
|
408
|
+
Edition::DOMAIN,
|
409
|
+
Edition::ASNUM,
|
410
|
+
Edition::ACCURACYRADIUS,
|
411
|
+
Edition::NETSPEED,
|
412
|
+
Edition::USERTYPE,
|
413
|
+
Edition::REGISTRAR,
|
414
|
+
Edition::LOCATIONA,
|
415
|
+
Edition::CITYCONF,
|
416
|
+
Edition::COUNTRYCONF,
|
417
|
+
Edition::REGIONCONF,
|
418
|
+
Edition::POSTALCONF
|
419
|
+
pos = seek_record(ipnum)
|
420
|
+
read_isp(pos-@database_segments[0])
|
421
|
+
else
|
352
422
|
throw "Invalid GeoIP database type, can't look up Organization/ISP by IP"
|
353
423
|
end
|
354
|
-
|
355
|
-
pos = seek_record(ipnum)
|
356
|
-
off = pos + (2*@record_length - 1) * @database_segments[0]
|
357
|
-
|
358
|
-
record = atomic_read(MAX_ORG_RECORD_LENGTH, off)
|
359
|
-
record = record.sub(/\000.*/n, '')
|
360
|
-
record.start_with?('*') ? nil : ISP.new(record)
|
361
424
|
end
|
362
425
|
|
363
426
|
# Search a ASN GeoIP database for the specified host, returning the AS
|
364
427
|
# number and description.
|
365
428
|
#
|
366
|
-
#
|
367
|
-
#
|
429
|
+
# Many other types of GeoIP database (e.g. userType) mis-identify as ASN type,
|
430
|
+
# and this can read those too.
|
431
|
+
#
|
432
|
+
# +hostname+ is a String holding the host's DNS name or numeric IP address.
|
368
433
|
#
|
369
434
|
# Returns the AS number and description.
|
370
435
|
#
|
@@ -377,18 +442,12 @@ class GeoIP
|
|
377
442
|
# Convert numeric IP address to an integer
|
378
443
|
ipnum = iptonum(ip)
|
379
444
|
|
380
|
-
if
|
381
|
-
throw "Invalid GeoIP database type, can't look up ASN by IP"
|
445
|
+
if ![Edition::ASNUM, Edition::ASNUM_V6].include? @database_type
|
446
|
+
throw "Invalid GeoIP database type #{@database_type}, can't look up ASN by IP"
|
382
447
|
end
|
383
448
|
|
384
449
|
pos = seek_record(ipnum)
|
385
|
-
|
386
|
-
|
387
|
-
record = atomic_read(MAX_ASN_RECORD_LENGTH, off)
|
388
|
-
record = record.sub(/\000.*/n, '')
|
389
|
-
|
390
|
-
# AS####, Description
|
391
|
-
ASN.new($1, $2) if record =~ /^(AS\d+)\s(.*)$/
|
450
|
+
read_asn(pos-@database_segments[0])
|
392
451
|
end
|
393
452
|
|
394
453
|
# Search a ISP GeoIP database for the specified host, returning the
|
@@ -401,12 +460,12 @@ class GeoIP
|
|
401
460
|
#
|
402
461
|
alias_method(:organization, :isp) # Untested, according to Maxmind docs this should work
|
403
462
|
|
404
|
-
# Iterate through a GeoIP city database
|
463
|
+
# Iterate through a GeoIP city database by
|
405
464
|
def each
|
406
465
|
return enum_for unless block_given?
|
407
466
|
|
408
|
-
if (@database_type !=
|
409
|
-
@database_type !=
|
467
|
+
if (@database_type != Edition::CITY_REV0 &&
|
468
|
+
@database_type != Edition::CITY_REV1)
|
410
469
|
throw "Invalid GeoIP database type, can't iterate thru non-City database"
|
411
470
|
end
|
412
471
|
|
@@ -422,8 +481,65 @@ class GeoIP
|
|
422
481
|
return self
|
423
482
|
end
|
424
483
|
|
484
|
+
# Call like this, for example:
|
485
|
+
# GeoIP.new('GeoIPNetSpeedCell.dat').each{|*a| puts("0x%08X\t%d" % a)}
|
486
|
+
# or:
|
487
|
+
# GeoIP.new('GeoIPv6.dat').each{|*a| puts("0x%032X\t%d" % a)}
|
488
|
+
def each_by_ip offset = 0, ipnum = 0, mask = nil, &callback
|
489
|
+
mask ||= 1 << (@ip_bits-1)
|
490
|
+
|
491
|
+
# Read the two pointers and split them:
|
492
|
+
record2 = atomic_read(@record_length*2, @record_length*2*offset)
|
493
|
+
record1 = record2.slice!(0, @record_length)
|
494
|
+
|
495
|
+
# Traverse the left tree
|
496
|
+
off1 = le_to_ui(record1.unpack('C*'))
|
497
|
+
val = off1 - @database_segments[0]
|
498
|
+
if val >= 0
|
499
|
+
yield(ipnum, read_record(ipnum.to_s, ipnum, val)) if val > 0
|
500
|
+
elsif mask != 0
|
501
|
+
each_by_ip(off1, ipnum, mask >> 1, &callback)
|
502
|
+
end
|
503
|
+
|
504
|
+
# Traverse the right tree
|
505
|
+
off2 = le_to_ui(record2.unpack('C*'))
|
506
|
+
val = off2 - @database_segments[0]
|
507
|
+
if val >= 0
|
508
|
+
yield(ipnum|mask, read_record(ipnum.to_s, ipnum, val)) if val > 0
|
509
|
+
elsif mask != 0
|
510
|
+
each_by_ip(off2, ipnum|mask, mask >> 1, &callback)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
425
514
|
private
|
426
515
|
|
516
|
+
def read_record hostname, ip, offset
|
517
|
+
case @database_type
|
518
|
+
when Edition::CITY_REV0, Edition::CITY_REV1, Edition::CITY_REV1_V6
|
519
|
+
read_city(offset, hostname, ip)
|
520
|
+
|
521
|
+
when Edition::REGION_REV0, Edition::REGION_REV1
|
522
|
+
read_region(offset, hostname, ip)
|
523
|
+
|
524
|
+
when Edition::NETSPEED, Edition::NETSPEED_REV1
|
525
|
+
read_netspeed(offset)
|
526
|
+
|
527
|
+
when Edition::COUNTRY, Edition::PROXY, Edition::COUNTRY_V6
|
528
|
+
read_country(offset, hostname, ip)
|
529
|
+
|
530
|
+
when Edition::ASNUM, Edition::ASNUM_V6
|
531
|
+
read_asn(offset)
|
532
|
+
|
533
|
+
# Add new types here
|
534
|
+
when Edition::ISP, Edition::ORG
|
535
|
+
read_isp offset
|
536
|
+
|
537
|
+
else
|
538
|
+
#raise "Unsupported GeoIP database type #{@database_type}"
|
539
|
+
offset
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
427
543
|
# Loads data into a StringIO which is Copy-on-write friendly
|
428
544
|
def preload_data
|
429
545
|
@file.seek(0)
|
@@ -434,57 +550,130 @@ class GeoIP
|
|
434
550
|
# Detects the type of the database.
|
435
551
|
def detect_database_type! # :nodoc:
|
436
552
|
@file.seek(-3, IO::SEEK_END)
|
553
|
+
@ip_bits = 32
|
437
554
|
|
438
555
|
0.upto(STRUCTURE_INFO_MAX_SIZE - 1) do |i|
|
439
556
|
if @file.read(3).bytes.all? { |byte| byte == 255 }
|
440
|
-
@database_type =
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
557
|
+
@database_type =
|
558
|
+
if @file.respond_to?(:getbyte)
|
559
|
+
@file.getbyte
|
560
|
+
else
|
561
|
+
@file.getc
|
562
|
+
end
|
445
563
|
|
446
564
|
@database_type -= 105 if @database_type >= 106
|
447
565
|
|
448
|
-
if (@database_type ==
|
566
|
+
if (@database_type == Edition::REGION_REV0)
|
449
567
|
# Region Edition, pre June 2003
|
450
568
|
@database_segments = [STATE_BEGIN_REV0]
|
451
|
-
elsif (@database_type ==
|
569
|
+
elsif (@database_type == Edition::REGION_REV1)
|
452
570
|
# Region Edition, post June 2003
|
453
571
|
@database_segments = [STATE_BEGIN_REV1]
|
454
|
-
elsif
|
455
|
-
@database_type ==
|
456
|
-
@database_type ==
|
457
|
-
@database_type ==
|
458
|
-
@database_type ==
|
459
|
-
@database_type ==
|
572
|
+
elsif @database_type == Edition::CITY_REV0 ||
|
573
|
+
@database_type == Edition::CITY_REV1 ||
|
574
|
+
@database_type == Edition::ORG ||
|
575
|
+
@database_type == Edition::ORG_V6 ||
|
576
|
+
@database_type == Edition::ISP ||
|
577
|
+
@database_type == Edition::ISP_V6 ||
|
578
|
+
@database_type == Edition::REGISTRAR ||
|
579
|
+
@database_type == Edition::REGISTRAR_V6 ||
|
580
|
+
@database_type == Edition::USERTYPE || # Many of these files mis-identify as ASNUM files
|
581
|
+
@database_type == Edition::USERTYPE_V6 ||
|
582
|
+
@database_type == Edition::DOMAIN ||
|
583
|
+
@database_type == Edition::DOMAIN_V6 ||
|
584
|
+
@database_type == Edition::ASNUM ||
|
585
|
+
@database_type == Edition::ASNUM_V6 ||
|
586
|
+
@database_type == Edition::NETSPEED_REV1 ||
|
587
|
+
@database_type == Edition::NETSPEED_REV1_V6 ||
|
588
|
+
@database_type == Edition::LOCATIONA ||
|
589
|
+
# @database_type == Edition::LOCATIONA_V6 ||
|
590
|
+
@database_type == Edition::ACCURACYRADIUS ||
|
591
|
+
@database_type == Edition::ACCURACYRADIUS_V6 ||
|
592
|
+
@database_type == Edition::CITYCONF ||
|
593
|
+
@database_type == Edition::COUNTRYCONF ||
|
594
|
+
@database_type == Edition::REGIONCONF ||
|
595
|
+
@database_type == Edition::POSTALCONF ||
|
596
|
+
@database_type == Edition::CITY_REV0_V6 ||
|
597
|
+
@database_type == Edition::CITY_REV1_V6
|
460
598
|
|
461
599
|
# City/Org Editions have two segments, read offset of second segment
|
462
600
|
@database_segments = [0]
|
463
601
|
sr = @file.read(3).unpack("C*")
|
464
602
|
@database_segments[0] += le_to_ui(sr)
|
465
603
|
|
466
|
-
if (@database_type == GEOIP_ORG_EDITION ||
|
467
|
-
@database_type == GEOIP_ISP_EDITION)
|
468
|
-
@record_length = 4
|
469
|
-
end
|
470
604
|
end
|
471
605
|
|
606
|
+
case @database_type
|
607
|
+
when Edition::NETSPEED_REV1
|
608
|
+
when Edition::ASNUM
|
609
|
+
when Edition::CITY_REV0
|
610
|
+
when Edition::CITY_REV1
|
611
|
+
when Edition::REGION_REV0
|
612
|
+
when Edition::REGION_REV1
|
613
|
+
@ip_bits = 32
|
614
|
+
@record_length = 3
|
615
|
+
|
616
|
+
when Edition::ORG,
|
617
|
+
Edition::DOMAIN,
|
618
|
+
Edition::ISP
|
619
|
+
@ip_bits = 32
|
620
|
+
@record_length = 4
|
621
|
+
|
622
|
+
when Edition::ASNUM_V6,
|
623
|
+
Edition::CITY_REV0_V6,
|
624
|
+
Edition::CITY_REV1_V6,
|
625
|
+
Edition::NETSPEED_REV1_V6,
|
626
|
+
Edition::COUNTRY,
|
627
|
+
Edition::COUNTRY_V6,
|
628
|
+
Edition::PROXY
|
629
|
+
@ip_bits = 128
|
630
|
+
@record_length = 3
|
631
|
+
|
632
|
+
when Edition::ACCURACYRADIUS_V6,
|
633
|
+
Edition::DOMAIN_V6,
|
634
|
+
Edition::ISP_V6,
|
635
|
+
Edition::LARGE_COUNTRY_V6,
|
636
|
+
Edition::LOCATIONA_V6,
|
637
|
+
Edition::ORG_V6,
|
638
|
+
Edition::REGISTRAR_V6,
|
639
|
+
Edition::USERTYPE_V6
|
640
|
+
@ip_bits = 128
|
641
|
+
@record_length = 4
|
642
|
+
|
643
|
+
else
|
644
|
+
raise "unimplemented database type"
|
645
|
+
end
|
646
|
+
|
472
647
|
break
|
473
648
|
else
|
474
649
|
@file.seek(-4, IO::SEEK_CUR)
|
475
650
|
end
|
476
651
|
end
|
477
652
|
|
478
|
-
if (@database_type ==
|
479
|
-
@database_type ==
|
480
|
-
@database_type ==
|
481
|
-
@database_type ==
|
653
|
+
if (@database_type == Edition::COUNTRY ||
|
654
|
+
@database_type == Edition::PROXY ||
|
655
|
+
@database_type == Edition::COUNTRY_V6 ||
|
656
|
+
@database_type == Edition::NETSPEED)
|
482
657
|
@database_segments = [COUNTRY_BEGIN]
|
483
658
|
end
|
659
|
+
|
660
|
+
# puts "Detected IPv#{@ip_bits == 32 ? '4' : '6'} database_type #{@database_type} with #{@database_segments[0]} records of length #{@record_length} (data starts at #{@database_segments[0]*@record_length*2})"
|
661
|
+
end
|
662
|
+
|
663
|
+
def read_country code, hostname, ip
|
664
|
+
Country.new(
|
665
|
+
hostname, # Requested hostname
|
666
|
+
ip, # Ip address as dotted quad
|
667
|
+
code, # GeoIP's country code
|
668
|
+
CountryCode[code], # ISO3166-1 alpha-2 code
|
669
|
+
CountryCode3[code], # ISO3166-2 alpha-3 code
|
670
|
+
CountryName[code], # Country name, per ISO 3166
|
671
|
+
CountryContinent[code] # Continent code.
|
672
|
+
)
|
484
673
|
end
|
485
674
|
|
486
675
|
def read_region(pos, hostname = '', ip = '') #:nodoc:
|
487
|
-
if (@database_type ==
|
676
|
+
if (@database_type == Edition::REGION_REV0)
|
488
677
|
pos -= STATE_BEGIN_REV0
|
489
678
|
if (pos >= 1000)
|
490
679
|
code = 225
|
@@ -493,7 +682,7 @@ class GeoIP
|
|
493
682
|
code = pos
|
494
683
|
region_code = ''
|
495
684
|
end
|
496
|
-
elsif (@database_type ==
|
685
|
+
elsif (@database_type == Edition::REGION_REV1)
|
497
686
|
pos -= STATE_BEGIN_REV1
|
498
687
|
if (pos < US_OFFSET)
|
499
688
|
code = 0
|
@@ -523,6 +712,40 @@ class GeoIP
|
|
523
712
|
)
|
524
713
|
end
|
525
714
|
|
715
|
+
def read_asn off
|
716
|
+
record = atomic_read(MAX_ASN_RECORD_LENGTH, off+index_size)
|
717
|
+
record.slice!(record.index("\0")..-1)
|
718
|
+
|
719
|
+
# AS####, Description
|
720
|
+
# REVISIT: Text is in Latin-1 (ISO8859-1)
|
721
|
+
if record =~ /^(AS\d+)(?:\s(.*))?$/
|
722
|
+
ASN.new($1, $2)
|
723
|
+
else
|
724
|
+
record
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def read_netspeed(offset)
|
729
|
+
return offset if @database_type == Edition::NETSPEED # Numeric value
|
730
|
+
|
731
|
+
record = atomic_read(20, index_size+offset)
|
732
|
+
record.slice!(record.index("\0")..-1)
|
733
|
+
record
|
734
|
+
end
|
735
|
+
|
736
|
+
def read_isp pos
|
737
|
+
off = pos + index_size
|
738
|
+
|
739
|
+
record = atomic_read(MAX_ORG_RECORD_LENGTH, off)
|
740
|
+
record = record.sub(/\000.*/n, '')
|
741
|
+
record.start_with?('*') ? nil : ISP.new(record)
|
742
|
+
end
|
743
|
+
|
744
|
+
# Size of the database index (a binary tree of depth <= @ip_bits)
|
745
|
+
def index_size
|
746
|
+
2 * @record_length * @database_segments[0]
|
747
|
+
end
|
748
|
+
|
526
749
|
def lookup_region_name(country_iso2, region_code)
|
527
750
|
country_regions = RegionName[country_iso2]
|
528
751
|
country_regions && country_regions[region_code]
|
@@ -545,9 +768,7 @@ class GeoIP
|
|
545
768
|
# * The timezone name, if known
|
546
769
|
#
|
547
770
|
def read_city(pos, hostname = '', ip = '') #:nodoc:
|
548
|
-
|
549
|
-
record = atomic_read(FULL_RECORD_LENGTH, off)
|
550
|
-
|
771
|
+
record = atomic_read(FULL_RECORD_LENGTH, pos+index_size)
|
551
772
|
return unless (record && record.size == FULL_RECORD_LENGTH)
|
552
773
|
|
553
774
|
# The country code is the first byte:
|
@@ -565,7 +786,7 @@ class GeoIP
|
|
565
786
|
city = spl[1]
|
566
787
|
@iter_pos += (city.size + 1) unless @iter_pos.nil?
|
567
788
|
# set the correct encoding in ruby 1.9 compatible environments:
|
568
|
-
city.force_encoding('iso-8859-1') if city.respond_to?(:force_encoding)
|
789
|
+
city = city.force_encoding('iso-8859-1').encode('utf-8') if city.respond_to?(:force_encoding)
|
569
790
|
|
570
791
|
# Get the postal code:
|
571
792
|
postal_code = spl[2]
|
@@ -595,7 +816,7 @@ class GeoIP
|
|
595
816
|
# UNTESTED
|
596
817
|
if (record &&
|
597
818
|
record[0,3] &&
|
598
|
-
@database_type ==
|
819
|
+
@database_type == Edition::CITY_REV1 &&
|
599
820
|
CountryCode[code] == "US")
|
600
821
|
|
601
822
|
dmaarea_combo = le_to_ui(record[0,3].unpack('C*'))
|
@@ -661,46 +882,18 @@ class GeoIP
|
|
661
882
|
# Binary search in the file.
|
662
883
|
# Records are pairs of little-endian integers, each of @record_length.
|
663
884
|
offset = 0
|
664
|
-
mask =
|
665
|
-
|
666
|
-
31.downto(0) do |depth|
|
667
|
-
off = (@record_length * 2 * offset)
|
668
|
-
buf = atomic_read(@record_length * 2, off)
|
669
|
-
|
670
|
-
buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
|
671
|
-
offset = le_to_ui(buf[0...@record_length].unpack("C*"))
|
885
|
+
mask = 1 << (@ip_bits-1)
|
672
886
|
|
673
|
-
|
674
|
-
|
675
|
-
|
887
|
+
@ip_bits.downto(1) do |depth|
|
888
|
+
go_right = (ipnum & mask) != 0
|
889
|
+
off = @record_length * (2 * offset + (go_right ? 1 : 0))
|
890
|
+
offset = le_to_ui(r2 = atomic_read(@record_length, off).unpack('C*'))
|
676
891
|
|
892
|
+
return offset if offset >= @database_segments[0]
|
677
893
|
mask >>= 1
|
678
894
|
end
|
679
895
|
end
|
680
896
|
|
681
|
-
def seek_record_v6(ipnum)
|
682
|
-
|
683
|
-
# Binary search in the file.
|
684
|
-
# Records are pairs of little-endian integers, each of @record_length.
|
685
|
-
offset = 0
|
686
|
-
mask = 1 << 127
|
687
|
-
|
688
|
-
127.downto(0) do |depth|
|
689
|
-
off = (@record_length * 2 * offset)
|
690
|
-
buf = atomic_read(@record_length * 2, off)
|
691
|
-
|
692
|
-
buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
|
693
|
-
offset = le_to_ui(buf[0...@record_length].unpack("C*"))
|
694
|
-
|
695
|
-
if (offset >= @database_segments[0])
|
696
|
-
return offset
|
697
|
-
end
|
698
|
-
|
699
|
-
mask >>= 1
|
700
|
-
end
|
701
|
-
|
702
|
-
end
|
703
|
-
|
704
897
|
# Convert a big-endian array of numeric bytes to unsigned int.
|
705
898
|
#
|
706
899
|
# Returns the unsigned Integer.
|
data/test/test_file.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# This program walks the specified file and dumps it in IP order
|
3
|
+
#
|
4
|
+
require 'geoip'
|
5
|
+
|
6
|
+
ARGV.each do |file|
|
7
|
+
g = GeoIP.new(file)
|
8
|
+
g.each_by_ip do |ip, val|
|
9
|
+
ip_str =
|
10
|
+
if ip >= (1<<32)
|
11
|
+
(('%032X'%ip).scan(/..../)*':').sub(/\A(0000:)+/, '::') # An IPv6 address
|
12
|
+
else
|
13
|
+
'%d.%d.%d.%d' % [ip].pack('N').unpack('C4')
|
14
|
+
end
|
15
|
+
puts "#{ip_str}\t#{val.to_hash.to_a.sort.map{|n,v| "#{n}=#{v.inspect}"}*', '}"
|
16
|
+
end
|
17
|
+
end
|
data/test/test_geoip.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
-
|
3
|
-
class TestGeoip < Test
|
4
|
-
|
5
|
-
def setup
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestGeoip < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
data = '/usr/share/GeoIP/GeoIP.dat'
|
7
|
+
begin
|
8
|
+
@g = GeoIP.new(data)
|
9
|
+
rescue Errno::ENOENT => e
|
10
|
+
skip e.message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_constructor
|
15
|
+
assert_instance_of GeoIP, @g
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
require '
|
2
|
+
require 'minitest/autorun'
|
3
3
|
require File.dirname(__FILE__) + '/../lib/geoip'
|
4
|
+
|
5
|
+
if Minitest.const_defined?('Test')
|
6
|
+
# We're on Minitest 5+. Nothing to do here.
|
7
|
+
else
|
8
|
+
# Minitest 4 doesn't have Minitest::Test yet.
|
9
|
+
Minitest::Test = MiniTest::Unit::TestCase
|
10
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geoip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clifford Heath
|
@@ -9,13 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-03-10 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
15
|
-
|
14
|
+
description: |-
|
15
|
+
GeoIP searches a GeoIP database for a given host or IP address, and
|
16
16
|
returns information about the country where the IP address is allocated,
|
17
|
-
|
18
|
-
and the city, ISP and other information, if you have that database version.'
|
17
|
+
and the city, ISP and other information, if you have that database version.
|
19
18
|
email:
|
20
19
|
- clifford.heath@gmail.com
|
21
20
|
- rmoriz@gmail.com
|
@@ -42,6 +41,7 @@ files:
|
|
42
41
|
- test/csvORG2dat.py
|
43
42
|
- test/organizations.csv
|
44
43
|
- test/organizations.dat
|
44
|
+
- test/test_file.rb
|
45
45
|
- test/test_geoip.rb
|
46
46
|
- test/test_helper.rb
|
47
47
|
homepage: http://github.com/cjheath/geoip
|
@@ -54,12 +54,12 @@ require_paths:
|
|
54
54
|
- lib
|
55
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- -
|
57
|
+
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: '0'
|
60
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
61
|
requirements:
|
62
|
-
- -
|
62
|
+
- - ">="
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|