geoip 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTNjMGMyYjJlNmYzY2JmZTFlZTI2ZDMwYmJiMWZjODVjZjA1ZTI4NQ==
5
- data.tar.gz: !binary |-
6
- NTIwNDVkMzA2NWI3YjNjODA2ODY5ZDkzZTY3MDI0NDljYjMwNTYxNw==
2
+ SHA1:
3
+ metadata.gz: 8e46df24a662de22a8500c265c5eaaf2bfb0cb29
4
+ data.tar.gz: d90310294059804b56d80ac41554fa68826393a2
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NThlNDBhZGYwOGFlODY3ODQ1NzBlZDQ1OGI3NzVkYzAzY2ZmOTg5OTEwYTFm
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 = ([GeoIP::GEOIP_CITY_EDITION_REV1, GeoIP::GEOIP_CITY_EDITION_REV0].include?(geoip.databaseType)) ? :city : :country
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) }
@@ -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.4.0 ruby lib
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.4.0"
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 = "2014-03-26"
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
  ]
@@ -58,7 +58,7 @@ require 'yaml'
58
58
  class GeoIP
59
59
 
60
60
  # The GeoIP GEM version number
61
- VERSION = "1.4.0"
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 = GEOIP_COUNTRY_EDITION
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
- if (@database_type == GEOIP_CITY_EDITION_REV0 ||
214
- @database_type == GEOIP_CITY_EDITION_REV1 ||
215
- @database_type == GEOIP_CITY_EDITION_REV1_V6)
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
- if (@database_type == GEOIP_REGION_EDITION_REV0 ||
220
- @database_type == GEOIP_REGION_EDITION_REV1)
221
- return region(hostname)
222
- end
266
+ when Edition::REGION_REV0, Edition::REGION_REV1
267
+ region(hostname)
223
268
 
224
- ip = lookup_ip(hostname)
225
- if (@database_type == GEOIP_COUNTRY_EDITION ||
226
- @database_type == GEOIP_PROXY_EDITION ||
227
- @database_type == GEOIP_NETSPEED_EDITION)
228
- # Convert numeric IP address to an integer
229
- ipnum = iptonum(ip)
230
- code = (seek_record(ipnum) - COUNTRY_BEGIN)
231
- elsif @database_type == GEOIP_COUNTRY_EDITION_V6
232
- ipaddr = IPAddr.new ip
233
- code = (seek_record_v6(ipaddr.to_i) - COUNTRY_BEGIN)
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
- Country.new(
239
- hostname, # Requested hostname
240
- ip, # Ip address as dotted quad
241
- code, # GeoIP's country code
242
- CountryCode[code], # ISO3166-1 alpha-2 code
243
- CountryCode3[code], # ISO3166-2 alpha-3 code
244
- CountryName[code], # Country name, per ISO 3166
245
- CountryContinent[code] # Continent code.
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, retuning region info.
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 == GEOIP_CITY_EDITION_REV0 ||
266
- @database_type == GEOIP_CITY_EDITION_REV1 ||
267
- @database_type == GEOIP_CITY_EDITION_REV1_V6)
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 == GEOIP_REGION_EDITION_REV0 ||
272
- @database_type == GEOIP_REGION_EDITION_REV1)
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 == GEOIP_CITY_EDITION_REV0 ||
310
- @database_type == GEOIP_CITY_EDITION_REV1)
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 == GEOIP_CITY_EDITION_REV1_V6)
369
+ elsif (@database_type == Edition::CITY_REV1_V6)
315
370
  ipaddr = IPAddr.new ip
316
- pos = seek_record_v6(ipaddr.to_i)
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
- if (@database_type != GEOIP_ISP_EDITION &&
351
- @database_type != GEOIP_ORG_EDITION)
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
- # +hostname+ is a String holding the host's DNS name or numeric
367
- # IP address.
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 (@database_type != GEOIP_ASNUM_EDITION)
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
- off = pos + (2*@record_length - 1) * @database_segments[0]
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 != GEOIP_CITY_EDITION_REV0 &&
409
- @database_type != GEOIP_CITY_EDITION_REV1)
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 = if @file.respond_to?(:getbyte)
441
- @file.getbyte
442
- else
443
- @file.getc
444
- end
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 == GEOIP_REGION_EDITION_REV0)
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 == GEOIP_REGION_EDITION_REV1)
569
+ elsif (@database_type == Edition::REGION_REV1)
452
570
  # Region Edition, post June 2003
453
571
  @database_segments = [STATE_BEGIN_REV1]
454
- elsif (@database_type == GEOIP_CITY_EDITION_REV0 ||
455
- @database_type == GEOIP_CITY_EDITION_REV1 ||
456
- @database_type == GEOIP_CITY_EDITION_REV1_V6 ||
457
- @database_type == GEOIP_ORG_EDITION ||
458
- @database_type == GEOIP_ISP_EDITION ||
459
- @database_type == GEOIP_ASNUM_EDITION)
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 == GEOIP_COUNTRY_EDITION ||
479
- @database_type == GEOIP_PROXY_EDITION ||
480
- @database_type == GEOIP_COUNTRY_EDITION_V6 ||
481
- @database_type == GEOIP_NETSPEED_EDITION)
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 == GEOIP_REGION_EDITION_REV0)
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 == GEOIP_REGION_EDITION_REV1)
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
- off = pos + (2*@record_length - 1) * @database_segments[0]
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 == GEOIP_CITY_EDITION_REV1 &&
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 = 0x80000000
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
- if (offset >= @database_segments[0])
674
- return offset
675
- end
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.
@@ -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
@@ -1,11 +1,18 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
- class TestGeoip < Test::Unit::TestCase
4
-
5
- def setup
6
- end
7
-
8
- def test_truth
9
- assert true
10
- end
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
@@ -1,3 +1,10 @@
1
1
  require 'stringio'
2
- require 'test/unit'
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.0
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: 2014-03-26 00:00:00.000000000 Z
12
+ date: 2015-03-10 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: ! 'GeoIP searches a GeoIP database for a given host or IP address, and
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: []