geoip 0.1.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 (2) hide show
  1. data/lib/geoip.rb +326 -0
  2. metadata +44 -0
data/lib/geoip.rb ADDED
@@ -0,0 +1,326 @@
1
+ #
2
+ # Native Ruby reader for the GeoIP database
3
+ # Lookup the country where IP address is allocated
4
+ #
5
+ #= COPYRIGHT
6
+ # This version Copyright (C) 2005 Clifford Heath
7
+ # Derived from the C version, Copyright (C) 2003 MaxMind LLC
8
+ #
9
+ # This library is free software; you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public
11
+ # License as published by the Free Software Foundation; either
12
+ # version 2.1 of the License, or (at your option) any later version.
13
+ #
14
+ # This library is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
+ # General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public
20
+ # License along with this library; if not, write to the Free Software
21
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
+ #
23
+ #= SYNOPSIS
24
+ #
25
+ # require 'geoip'
26
+ # p GeoIP.new('/usr/share/GeoIP/GeoIP.dat').country("www.netscape.sk")
27
+ #
28
+ #= DESCRIPTION
29
+ #
30
+ # GeoIP searches a GeoIP database for a given host or IP address, and
31
+ # returns information about the country where the IP address is allocated.
32
+ #
33
+ #= PREREQUISITES
34
+ #
35
+ # You need at least the free GeoIP.dat, for which the last known download
36
+ # location is <http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz>
37
+ # This API requires the file to be decompressed for searching. Other versions
38
+ # of this database are available for purchase which contain more detailed
39
+ # information, but this information is not returned by this implementation.
40
+ # See www.maxmind.com for more information.
41
+ #
42
+ #=end
43
+
44
+ require 'socket'
45
+
46
+ class GeoIP
47
+ private
48
+ CountryCode = [
49
+ "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR",
50
+ "AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ",
51
+ "BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF",
52
+ "CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ",
53
+ "DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI",
54
+ "FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL",
55
+ "GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR",
56
+ "HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP",
57
+ "KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC",
58
+ "LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK",
59
+ "ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY",
60
+ "MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM",
61
+ "PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY",
62
+ "QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ",
63
+ "SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG",
64
+ "TH","TJ","TK","TM","TN","TO","TP","TR","TT","TV","TW","TZ","UA","UG",
65
+ "UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE",
66
+ "YT","YU","ZA","ZM","ZR","ZW","A1","A2","O1" ]
67
+
68
+ CountryCode3 = [
69
+ "--","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT","AGO",
70
+ "AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA",
71
+ "BGR","BHR","BDI","BEN","BMU","BRN","BOL","BRA","BHS","BTN","BV","BWA",
72
+ "BLR","BLZ","CAN","CC","COD","CAF","COG","CHE","CIV","COK","CHL","CMR",
73
+ "CHN","COL","CRI","CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA",
74
+ "DOM","DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI","FLK",
75
+ "FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF","GHA","GIB","GRL",
76
+ "GMB","GIN","GLP","GNQ","GRC","GS","GTM","GUM","GNB","GUY","HKG","HM",
77
+ "HND","HRV","HTI","HUN","IDN","IRL","ISR","IND","IO","IRQ","IRN","ISL",
78
+ "ITA","JAM","JOR","JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR",
79
+ "KWT","CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU","LUX",
80
+ "LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI","MMR","MNG","MAC",
81
+ "MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS","MOZ","NAM",
82
+ "NCL","NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN",
83
+ "PAN","PER","PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT",
84
+ "PLW","PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE",
85
+ "SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV",
86
+ "SYR","SWZ","TCA","TCD","TF","TGO","THA","TJK","TKL","TLS","TKM","TUN",
87
+ "TON","TUR","TTO","TUV","TWN","TZA","UKR","UGA","UM","USA","URY","UZB",
88
+ "VAT","VCT","VEN","VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","YUG",
89
+ "ZAF","ZMB","ZR","ZWE","A1","A2","O1"]
90
+
91
+ CountryName = [
92
+ "N/A","Asia/Pacific Region","Europe","Andorra","United Arab Emirates",
93
+ "Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia",
94
+ "Netherlands Antilles","Angola","Antarctica","Argentina",
95
+ "American Samoa","Austria","Australia","Aruba","Azerbaijan",
96
+ "Bosnia and Herzegovina","Barbados","Bangladesh","Belgium",
97
+ "Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda",
98
+ "Brunei Darussalam","Bolivia","Brazil","Bahamas","Bhutan",
99
+ "Bouvet Island","Botswana","Belarus","Belize","Canada",
100
+ "Cocos (Keeling) Islands","Congo, The Democratic Republic of the",
101
+ "Central African Republic","Congo","Switzerland","Cote D'Ivoire",
102
+ "Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica",
103
+ "Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic",
104
+ "Germany","Djibouti","Denmark","Dominica","Dominican Republic",
105
+ "Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea",
106
+ "Spain","Ethiopia","Finland","Fiji","Falkland Islands (Malvinas)",
107
+ "Micronesia, Federated States of","Faroe Islands","France","France,
108
+ Metropolitan","Gabon","United Kingdom","Grenada","Georgia",
109
+ "French Guiana","Ghana","Gibraltar","Greenland","Gambia","Guinea",
110
+ "Guadeloupe","Equatorial Guinea","Greece",
111
+ "South Georgia and the South Sandwich Islands","Guatemala","Guam",
112
+ "Guinea-Bissau","Guyana","Hong Kong",
113
+ "Heard Island and McDonald Islands","Honduras","Croatia","Haiti",
114
+ "Hungary","Indonesia","Ireland","Israel","India",
115
+ "British Indian Ocean Territory","Iraq","Iran, Islamic Republic of",
116
+ "Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan",
117
+ "Cambodia","Kiribati","Comoros","Saint Kitts and Nevis",
118
+ "Korea, Democratic People's Republic of","Korea, Republic of","Kuwait",
119
+ "Cayman Islands","Kazakhstan","Lao People's Democratic Republic",
120
+ "Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho",
121
+ "Lithuania","Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco",
122
+ "Monaco","Moldova, Republic of","Madagascar","Marshall Islands",
123
+ "Macedonia","Mali","Myanmar","Mongolia","Macau",
124
+ "Northern Mariana Islands","Martinique","Mauritania","Montserrat",
125
+ "Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia",
126
+ "Mozambique","Namibia","New Caledonia","Niger","Norfolk Island",
127
+ "Nigeria","Nicaragua","Netherlands","Norway","Nepal","Nauru","Niue",
128
+ "New Zealand","Oman","Panama","Peru","French Polynesia",
129
+ "Papua New Guinea","Philippines","Pakistan","Poland",
130
+ "Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico",
131
+ "Palestinian Territory, Occupied","Portugal","Palau","Paraguay","Qatar",
132
+ "Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia",
133
+ "Solomon Islands","Seychelles","Sudan","Sweden","Singapore",
134
+ "Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia",
135
+ "Sierra Leone","San Marino","Senegal","Somalia","Suriname",
136
+ "Sao Tome and Principe","El Salvador","Syrian Arab Republic",
137
+ "Swaziland","Turks and Caicos Islands","Chad",
138
+ "French Southern Territories","Togo","Thailand","Tajikistan","Tokelau",
139
+ "Turkmenistan","Tunisia","Tonga","East Timor","Turkey",
140
+ "Trinidad and Tobago","Tuvalu","Taiwan","Tanzania, United Republic of",
141
+ "Ukraine","Uganda","United States Minor Outlying Islands",
142
+ "United States","Uruguay","Uzbekistan","Holy See (Vatican City State)",
143
+ "Saint Vincent and the Grenadines","Venezuela","Virgin Islands,
144
+ British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna",
145
+ "Samoa","Yemen","Mayotte","Yugoslavia","South Africa","Zambia","Zaire",
146
+ "Zimbabwe","Anonymous Proxy","Satellite Provider","Other"]
147
+
148
+ CountryContinent = [
149
+ "--","AS","EU","EU","AS","AS","SA","SA","EU","AS","SA","AF","AN","SA",
150
+ "OC","EU","OC","SA","AS","EU","SA","AS","EU","AF","EU","AS","AF","AF",
151
+ "SA","AS","SA","SA","SA","AS","AF","AF","EU","SA","NA","AS","AF","AF",
152
+ "AF","EU","AF","OC","SA","AF","AS","SA","SA","SA","AF","AS","AS","EU",
153
+ "EU","AF","EU","SA","SA","AF","SA","EU","AF","AF","AF","EU","AF","EU",
154
+ "OC","SA","OC","EU","EU","EU","AF","EU","SA","AS","SA","AF","EU","SA",
155
+ "AF","AF","SA","AF","EU","SA","SA","OC","AF","SA","AS","AF","SA","EU",
156
+ "SA","EU","AS","EU","AS","AS","AS","AS","AS","EU","EU","SA","AS","AS",
157
+ "AF","AS","AS","OC","AF","SA","AS","AS","AS","SA","AS","AS","AS","SA",
158
+ "EU","AS","AF","AF","EU","EU","EU","AF","AF","EU","EU","AF","OC","EU",
159
+ "AF","AS","AS","AS","OC","SA","AF","SA","EU","AF","AS","AF","NA","AS",
160
+ "AF","AF","OC","AF","OC","AF","SA","EU","EU","AS","OC","OC","OC","AS",
161
+ "SA","SA","OC","OC","AS","AS","EU","SA","OC","SA","AS","EU","OC","SA",
162
+ "AS","AF","EU","AS","AF","AS","OC","AF","AF","EU","AS","AF","EU","EU",
163
+ "EU","AF","EU","AF","AF","SA","AF","SA","AS","AF","SA","AF","AF","AF",
164
+ "AS","AS","OC","AS","AF","OC","AS","AS","SA","OC","AS","AF","EU","AF",
165
+ "OC","NA","SA","AS","EU","SA","SA","SA","SA","AS","OC","OC","OC","AS",
166
+ "AF","EU","AF","AF","AF","AF"]
167
+
168
+ private
169
+ # Edition enumeration:
170
+ (GEOIP_COUNTRY_EDITION,
171
+ GEOIP_CITY_EDITION_REV1,
172
+ GEOIP_REGION_EDITION_REV1,
173
+ GEOIP_ISP_EDITION,
174
+ GEOIP_ORG_EDITION,
175
+ GEOIP_CITY_EDITION_REV0,
176
+ GEOIP_REGION_EDITION_REV0,
177
+ GEOIP_PROXY_EDITION,
178
+ GEOIP_ASNUM_EDITION,
179
+ GEOIP_NETSPEED_EDITION,
180
+ ) = *1..10
181
+
182
+ COUNTRY_BEGIN = 16776960
183
+ STATE_BEGIN_REV0 = 16700000
184
+ STATE_BEGIN_REV1 = 16000000
185
+ STRUCTURE_INFO_MAX_SIZE = 20
186
+ DATABASE_INFO_MAX_SIZE = 100
187
+ MAX_ORG_RECORD_LENGTH = 300
188
+ US_OFFSET = 1
189
+ CANADA_OFFSET = 677
190
+ WORLD_OFFSET = 1353
191
+ FIPS_RANGE = 360
192
+
193
+ STANDARD_RECORD_LENGTH = 3
194
+ SEGMENT_RECORD_LENGTH = 3
195
+
196
+ public
197
+ # Open the GeoIP database and determine the file format version
198
+ #
199
+ # +filename+ is a String holding the path to the GeoIP.dat file
200
+ # +options+ is an integer holding caching flags (unimplemented)
201
+ def initialize(filename, flags = 0)
202
+ @flags = flags
203
+ @databaseType = GEOIP_COUNTRY_EDITION
204
+ @record_length = STANDARD_RECORD_LENGTH
205
+ @file = File.open(filename, 'rb')
206
+ @file.seek(-31, IO::SEEK_END)
207
+ 1.upto(STRUCTURE_INFO_MAX_SIZE) {
208
+ if @file.read(3) == "\xFF\xFF\xFF"
209
+ @databaseType = @file.getc
210
+ @databaseType -= 105 if @databaseType >= 106
211
+
212
+ puts "Old database file type #{@databaseType}, untested"
213
+
214
+ if (@databaseType == GEOIP_REGION_EDITION_REV0)
215
+ # Region Edition, pre June 2003
216
+ @databaseSegments = [ STATE_BEGIN_REV0 ]
217
+ elsif (@databaseType == GEOIP_REGION_EDITION_REV1)
218
+ # Region Edition, post June 2003
219
+ @databaseSegments = [ STATE_BEGIN_REV1 ]
220
+ elsif (@databaseType == GEOIP_CITY_EDITION_REV0 ||
221
+ @databaseType == GEOIP_CITY_EDITION_REV1 ||
222
+ @databaseType == GEOIP_ORG_EDITION ||
223
+ @databaseType == GEOIP_ISP_EDITION ||
224
+ @databaseType == GEOIP_ASNUM_EDITION)
225
+ # City/Org Editions have two segments, read offset of second segment
226
+ @databaseSegments = [ 0 ]
227
+ sr = @file.read(3).unpack("C*")
228
+ @databaseSegments[0] += le_to_ui(sr)
229
+
230
+ if (@databaseType == GEOIP_ORG_EDITION ||
231
+ @databaseType == GEOIP_ISP_EDITION)
232
+ @record_length = 4
233
+ end
234
+ end
235
+
236
+ else
237
+ @file.seek(-41, IO::SEEK_CUR)
238
+ end
239
+ }
240
+ if (@databaseType == GEOIP_COUNTRY_EDITION ||
241
+ @databaseType == GEOIP_PROXY_EDITION ||
242
+ @databaseType == GEOIP_NETSPEED_EDITION)
243
+ @databaseSegments = [ COUNTRY_BEGIN ]
244
+ end
245
+ end
246
+
247
+ # Search the GeoIP database for the specified host, returning country info
248
+ #
249
+ # +hostname+ is a String holding the host's DNS name or numeric IP address
250
+ # Return an array of seven elements:
251
+ # * The host or IP address string as requested
252
+ # * The IP address string after looking up the host
253
+ # * The GeoIP country-ID as an integer
254
+ # * The ISO3166-1 two-character country code
255
+ # * The ISO3166-2 three-character country code
256
+ # * The ISO3166 English-language name of the country
257
+ # * The two-character continent code
258
+ #
259
+ def country(hostname)
260
+ ip = hostname
261
+ if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
262
+ # Lookup IP address, we were given a name
263
+ ip = IPSocket.getaddress(hostname)
264
+ end
265
+
266
+ # Convert numeric IP address to an integer
267
+ ipnum = iptonum(ip)
268
+ if (@databaseType != GEOIP_COUNTRY_EDITION &&
269
+ @databaseType != GEOIP_PROXY_EDITION &&
270
+ @databaseType != GEOIP_NETSPEED_EDITION)
271
+ throw "Invalid GeoIP database type, can't look up Country by IP"
272
+ end
273
+ code = seek_record(ipnum) - COUNTRY_BEGIN;
274
+ [ hostname, # Requested hostname
275
+ ip, # Ip address as dotted quad
276
+ code, # GeoIP's country code
277
+ CountryCode[code], # ISO3166-1 code
278
+ CountryCode3[code], # ISO3166-2 code
279
+ CountryName[code], # Country name, per IS03166
280
+ CountryContinent[code] ] # Continent code.
281
+ end
282
+
283
+ private
284
+ def iptonum(ip) # Convert numeric IP address to integer
285
+ if ip.kind_of?(String) &&
286
+ ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/
287
+ ip = be_to_ui(Regexp.last_match().to_a.slice(1..4))
288
+ end
289
+ ip
290
+ end
291
+
292
+ def seek_record(ipnum)
293
+ # Binary search in the file.
294
+ # Records are pairs of little-endian integers, each of @record_length.
295
+ offset = 0
296
+ mask = 2147483648
297
+ 31.downto(0) { |depth|
298
+ @file.seek(@record_length * 2 * offset);
299
+ buf = @file.read(@record_length*2);
300
+ buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
301
+ offset = le_to_ui(buf[0...@record_length].unpack("C*"))
302
+ return offset if (offset >= @databaseSegments[0])
303
+ mask = mask/2
304
+ }
305
+ end
306
+
307
+ # Convert a big-endian array of numeric bytes to unsigned int
308
+ def be_to_ui(s)
309
+ s.inject(0) { |m, o|
310
+ (m<<8)+o.to_i
311
+ }
312
+ end
313
+
314
+ # Same for little-endian
315
+ def le_to_ui(s)
316
+ be_to_ui(s.reverse)
317
+ end
318
+ end
319
+
320
+ if $0 == __FILE__
321
+ g = GeoIP.new('/usr/share/GeoIP/GeoIP.dat');
322
+
323
+ ARGV.each { |a|
324
+ p g.country(a)
325
+ }
326
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.4
3
+ specification_version: 1
4
+ name: geoip
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2005-03-11
8
+ summary: "GeoIP looks up a GeoIP database to provide geographical data for an IP address
9
+ or Internet hostname. The free version of the GeoIP database available from
10
+ www.maxmind.com only contains country information, and so far that's all that
11
+ this library supports. The data is much more reliable than using the country
12
+ codes at the end of the hosts' domain names."
13
+ require_paths:
14
+ - lib
15
+ email: cjh@polyplex.org
16
+ homepage: http://polyplex.org/cjh
17
+ rubyforge_project:
18
+ description: "GeoIP is a pure-Ruby replacement for some of the functionality in the wrapped C
19
+ library by Sean Chittenden. Given an IP address or hostname, it looks up the
20
+ country where that IP address is allocated."
21
+ autorequire: geoip
22
+ default_executable:
23
+ bindir: bin
24
+ has_rdoc: true
25
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
26
+ requirements:
27
+ -
28
+ - ">"
29
+ - !ruby/object:Gem::Version
30
+ version: 0.0.0
31
+ version:
32
+ platform: ruby
33
+ authors:
34
+ - Clifford Heath
35
+ files:
36
+ - lib/geoip.rb
37
+ test_files: []
38
+ rdoc_options: []
39
+ extra_rdoc_files: []
40
+ executables: []
41
+ extensions: []
42
+ requirements:
43
+ - The free GeoIP database from www.maxmind.com
44
+ dependencies: []