class GeoIP

Constants

CountryCode

Ordered list of the ISO3166 2-character country codes, ordered by GeoIP ID

CountryCode3

Ordered list of the ISO3166 3-character country codes, ordered by GeoIP ID

CountryContinent

Ordered list of the ISO3166 2-character continent code of the countries, ordered by GeoIP ID

CountryName

Ordered list of the English names of the countries, ordered by GeoIP ID

DATA_DIR

The data/ directory for geoip

GEOIP_ASNUM_EDITION
GEOIP_CABLEDSL_SPEED
GEOIP_CITY_EDITION_REV0
GEOIP_CITY_EDITION_REV1
GEOIP_CITY_EDITION_REV1_V6
GEOIP_CORPORATE_SPEED
GEOIP_COUNTRY_EDITION
GEOIP_COUNTRY_EDITION_V6
GEOIP_DIALUP_SPEED
GEOIP_ISP_EDITION
GEOIP_NETSPEED_EDITION
GEOIP_NETSPEED_EDITION_REV1
GEOIP_ORG_EDITION
GEOIP_PROXY_EDITION
GEOIP_REGION_EDITION_REV0
GEOIP_REGION_EDITION_REV1
GEOIP_UNKNOWN_SPEED

Numeric codes for NETSPEED (NETSPEED_REV1* is string-based):

RegionName

Load a hash of region names by region code

TimeZone

Hash of the timezone codes mapped to timezone name, per zoneinfo

VERSION

The GeoIP GEM version number

Attributes

databaseType[R]

The Edition number that identifies which kind of database you've opened

database_type[R]

The Edition number that identifies which kind of database you've opened

local_ip_alias[RW]

An IP that is used instead of local IPs

Public Class Methods

new(filename, options = {}) click to toggle source

Open the GeoIP database and determine the file format version.

filename is a String holding the path to the GeoIP.dat file options is a Hash allowing you to specify the caching options

# File lib/geoip.rb, line 225
def initialize(filename, options = {})
  if options[:preload] || !IO.respond_to?(:pread)
    @mutex = Mutex.new
  end

  @use_pread = IO.respond_to?(:pread) && !options[:preload]

  @options = options
  @database_type = Edition::COUNTRY
  @record_length = STANDARD_RECORD_LENGTH
  @file = File.open(filename, 'rb')

  detect_database_type!

  preload_data if options[:preload]
end

Public Instance Methods

asn(hostname) click to toggle source

Search a ASN GeoIP database for the specified host, returning the AS number and description.

Many other types of GeoIP database (e.g. userType) mis-identify as ASN type, and this can read those too.

hostname is a String holding the host's DNS name or numeric IP address.

Returns the AS number and description.

Source: geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz

# File lib/geoip.rb, line 439
def asn(hostname)
  ip = lookup_ip(hostname)

  # Convert numeric IP address to an integer
  ipnum = iptonum(ip)

  if ![Edition::ASNUM, Edition::ASNUM_V6].include? @database_type
    throw "Invalid GeoIP database type #{@database_type}, can't look up ASN by IP"
  end

  pos = seek_record(ipnum)
  read_asn(pos-@database_segments[0])
end
city(hostname) click to toggle source

Search the GeoIP database for the specified host, returning city info.

hostname is a String holding the host's DNS name or numeric IP address.

Returns a City object with the fourteen elements:

  • The host or IP address string as requested

  • The IP address string after looking up the host

  • The two-character country code (ISO 3166-1 alpha-2)

  • The three-character country code (ISO 3166-2 alpha-3)

  • The ISO 3166 English-language name of the country

  • The two-character continent code

  • The region name (state or territory)

  • The city name

  • The postal code (zipcode)

  • The latitude

  • The longitude

  • The USA dma_code if known (only REV1 City database)

  • The USA area_code if known (only REV1 City database)

  • The timezone name, if known

# File lib/geoip.rb, line 361
def city(hostname)
  ip = lookup_ip(hostname)

  if (@database_type == Edition::CITY_REV0 ||
      @database_type == Edition::CITY_REV1)
    # Convert numeric IP address to an integer
    ipnum = iptonum(ip)
    pos = seek_record(ipnum)
  elsif (@database_type == Edition::CITY_REV1_V6)
    ipaddr = IPAddr.new ip
    pos = seek_record(ipaddr.to_i)
  else
    throw "Invalid GeoIP database type, can't look up City by IP"
  end

  # This next statement was added to MaxMind's C version after it was
  # rewritten in Ruby. It prevents unassigned IP addresses from returning
  # bogus data.  There was concern over whether the changes to an
  # application's behaviour were always correct, but this has been tested
  # using an exhaustive search of the top 16 bits of the IP address space.
  # The records where the change takes effect contained *no* valid data. 
  # If you're concerned, email me, and I'll send you the test program so
  # you can test whatever IP range you think is causing problems,
  # as I don't care to undertake an exhaustive search of the 32-bit space.
  unless pos == @database_segments[0]
    read_city(pos-@database_segments[0], hostname, ip)
  end
end
country(hostname) click to toggle source

Search the GeoIP database for the specified host, returning country info.

hostname is a String holding the host's DNS name or numeric IP address.

If the database is a City database (normal), return the result that city would return.

Otherwise, return a Country object with the seven elements:

  • The host or IP address string as requested

  • The IP address string after looking up the host

  • The GeoIP country-ID as an integer (N.B. this is excluded from the city results!)

  • The two-character country code (ISO 3166-1 alpha-2)

  • The three-character country code (ISO 3166-2 alpha-3)

  • The ISO 3166 English-language name of the country

  • The two-character continent code

# File lib/geoip.rb, line 261
def country(hostname)
  case @database_type
  when Edition::CITY_REV0, Edition::CITY_REV1, Edition::CITY_REV1_V6
    city(hostname)

  when Edition::REGION_REV0, Edition::REGION_REV1
    region(hostname)

  when Edition::NETSPEED, Edition::NETSPEED_REV1
    netspeed(hostname)

  when Edition::COUNTRY, Edition::PROXY, Edition::COUNTRY_V6
    ip = lookup_ip(hostname)
    if @ip_bits > 32
      ipaddr = IPAddr.new ip
      code = (seek_record(ipaddr.to_i) - COUNTRY_BEGIN)
    else
      # Convert numeric IP address to an integer
      ipnum = iptonum(ip)
      code = (seek_record(ipnum) - @database_segments[0])
    end
    read_country(code, hostname, ip)
  else
    throw "Invalid GeoIP database type #{@database_type}, can't look up Country by IP"
  end
end
each() { |rec| ... } click to toggle source

Iterate through a GeoIP city database by

# File lib/geoip.rb, line 464
def each
  return enum_for unless block_given?

  if (@database_type != Edition::CITY_REV0 &&
      @database_type != Edition::CITY_REV1)
    throw "Invalid GeoIP database type, can't iterate thru non-City database"
  end

  @iter_pos = @database_segments[0] + 1
  num = 0

  until ((rec = read_city(@iter_pos)).nil?)
    yield rec
    print "#{num}: #{@iter_pos}\n" if((num += 1) % 1000 == 0)
  end

  @iter_pos = nil
  return self
end
each_by_ip(offset = 0, ipnum = 0, mask = nil) { |ipnum, read_record(ipnum, ipnum, val)| ... } click to toggle source

Call like this, for example: ::new('GeoIPNetSpeedCell.dat').each{|*a| puts(ā€œ0x%08Xt%dā€ % a)} or: ::new('GeoIPv6.dat').each{|*a| puts(ā€œ0x%032Xt%dā€ % a)}

# File lib/geoip.rb, line 488
def each_by_ip offset = 0, ipnum = 0, mask = nil, &callback
  mask ||= 1 << (@ip_bits-1)

  # Read the two pointers and split them:
  record2 = atomic_read(@record_length*2, @record_length*2*offset)
  record1 = record2.slice!(0, @record_length)

  # Traverse the left tree
  off1 = le_to_ui(record1.unpack('C*'))
  val = off1 - @database_segments[0]
  if val >= 0
    yield(ipnum, read_record(ipnum.to_s, ipnum, val)) if val > 0
  elsif mask != 0
    each_by_ip(off1, ipnum, mask >> 1, &callback)
  end

  # Traverse the right tree
  off2 = le_to_ui(record2.unpack('C*'))
  val = off2 - @database_segments[0]
  if val >= 0
    yield(ipnum|mask, read_record(ipnum.to_s, ipnum, val)) if val > 0
  elsif mask != 0
    each_by_ip(off2, ipnum|mask, mask >> 1, &callback)
  end
end
isp(hostname) click to toggle source

Search a ISP GeoIP database for the specified host, returning the ISP Not all GeoIP databases contain ISP information. Check maxmind.com

hostname is a String holding the host's DNS name or numeric IP address.

Returns the ISP name.

# File lib/geoip.rb, line 399
def isp(hostname)
  ip = lookup_ip(hostname)

  # Convert numeric IP address to an integer
  ipnum = iptonum(ip)

  case @database_type
  when Edition::ORG,
       Edition::ISP,
       Edition::DOMAIN,
       Edition::ASNUM,
       Edition::ACCURACYRADIUS,
       Edition::NETSPEED,
       Edition::USERTYPE,
       Edition::REGISTRAR,
       Edition::LOCATIONA,
       Edition::CITYCONF,
       Edition::COUNTRYCONF,
       Edition::REGIONCONF,
       Edition::POSTALCONF
    pos = seek_record(ipnum)
    read_isp(pos-@database_segments[0])
  else
    throw "Invalid GeoIP database type, can't look up Organization/ISP by IP"
  end
end
Also aliased as: organization
netspeed(hostname) click to toggle source

Search a GeoIP Connection Type (Netspeed) database for the specified host, returning the speed code.

hostname is a String holding the host's DNS name or numeric IP address.

# File lib/geoip.rb, line 292
def netspeed(hostname)
  unless (@database_type == Edition::NETSPEED ||
      @database_type == Edition::NETSPEED_REV1)
    throw "Invalid GeoIP database type #{@database_type}, can't look up Netspeed by IP"
  end
  # Convert numeric IP address to an integer
  ip = lookup_ip(hostname)
  ipnum = iptonum(ip)
  offset = (seek_record(ipnum) - @database_segments[0])
  read_netspeed(offset)
end
organization(hostname)

Search a ISP GeoIP database for the specified host, returning the organization.

hostname is a String holding the host's DNS name or numeric IP address.

Returns the organization associated with it.

Alias for: isp
region(hostname) click to toggle source

Search the GeoIP database for the specified host, returning region info.

hostname is a String holding the hosts's DNS name or numeric IP address.

Returns a Region object with the nine elements:

  • The host or IP address string as requested

  • The IP address string after looking up the host

  • The two-character country code (ISO 3166-1 alpha-2)

  • The three-character country code (ISO 3166-2 alpha-3)

  • The ISO 3166 English-language name of the country

  • The two-character continent code

  • The region name (state or territory)

  • The timezone name, if known

# File lib/geoip.rb, line 319
def region(hostname)
  if (@database_type == Edition::CITY_REV0 ||
      @database_type == Edition::CITY_REV1 ||
      @database_type == Edition::CITY_REV1_V6)
    return city(hostname)
  end

  if (@database_type == Edition::REGION_REV0 ||
      @database_type == Edition::REGION_REV1)
    ip = lookup_ip(hostname)
    ipnum = iptonum(ip)
    pos = seek_record(ipnum)
  else
    throw "Invalid GeoIP database type, can't look up Region by IP"
  end

  unless pos == @database_segments[0]
    read_region(pos, hostname, ip)
  end
end

Private Instance Methods

atomic_read_unguarded(length, offset) click to toggle source
# File lib/geoip.rb, line 928
def atomic_read_unguarded(length, offset)
  if @use_pread
    IO.pread(@file.fileno, length, offset)
  else
    io = @contents || @file
    io.seek(offset)
    io.read(length)
  end
end
index_size() click to toggle source

Size of the database index (a binary tree of depth <= @ip_bits)

# File lib/geoip.rb, line 745
def index_size
  2 * @record_length * @database_segments[0]
end
lookup_region_name(country_iso2, region_code) click to toggle source
# File lib/geoip.rb, line 749
def lookup_region_name(country_iso2, region_code)
  country_regions = RegionName[country_iso2]
  country_regions && country_regions[region_code]
end
preload_data() click to toggle source

Loads data into a StringIO which is Copy-on-write friendly

# File lib/geoip.rb, line 544
def preload_data
  @file.seek(0)
  @contents = StringIO.new(@file.read)
  @file.close
end
read_asn(off) click to toggle source
# File lib/geoip.rb, line 715
def read_asn off
  record = atomic_read(MAX_ASN_RECORD_LENGTH, off+index_size)
  record.slice!(record.index("\0")..-1)

  # AS####, Description
  # REVISIT: Text is in Latin-1 (ISO8859-1)
  if record =~ /^(AS\d+)(?:\s(.*))?$/
    ASN.new($1, $2)
  else
    record
  end
end
read_country(code, hostname, ip) click to toggle source
# File lib/geoip.rb, line 663
def read_country code, hostname, ip
  Country.new(
    hostname,                   # Requested hostname
    ip,                         # Ip address as dotted quad
    code,                       # GeoIP's country code
    CountryCode[code],          # ISO3166-1 alpha-2 code
    CountryCode3[code],         # ISO3166-2 alpha-3 code
    CountryName[code],          # Country name, per ISO 3166
    CountryContinent[code]      # Continent code.
  )
end
read_isp(pos) click to toggle source
# File lib/geoip.rb, line 736
def read_isp pos
  off = pos + index_size

  record = atomic_read(MAX_ORG_RECORD_LENGTH, off)
  record = record.sub(/\000.*/n, '')
  record.start_with?('*') ? nil : ISP.new(record)
end
read_netspeed(offset) click to toggle source
# File lib/geoip.rb, line 728
def read_netspeed(offset)
  return offset if @database_type == Edition::NETSPEED  # Numeric value

  record = atomic_read(20, index_size+offset)
  record.slice!(record.index("\0")..-1)
  record
end
read_record(hostname, ip, offset) click to toggle source
# File lib/geoip.rb, line 516
def read_record hostname, ip, offset
  case @database_type
  when Edition::CITY_REV0, Edition::CITY_REV1, Edition::CITY_REV1_V6
    read_city(offset, hostname, ip)

  when Edition::REGION_REV0, Edition::REGION_REV1
    read_region(offset, hostname, ip)

  when Edition::NETSPEED, Edition::NETSPEED_REV1
    read_netspeed(offset)

  when Edition::COUNTRY, Edition::PROXY, Edition::COUNTRY_V6
    read_country(offset, hostname, ip)

  when Edition::ASNUM, Edition::ASNUM_V6
    read_asn(offset)

  # Add new types here
  when Edition::ISP, Edition::ORG
    read_isp offset

  else
    #raise "Unsupported GeoIP database type #{@database_type}"
    offset
  end
end