geoipdb 0.1.4
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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +18 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/ext/build.sh +1 -0
- data/ext/extconf.rb +5 -0
- data/ext/geoipdb.c +109 -0
- data/ext/ipdb.c +604 -0
- data/ext/ipdb.h +69 -0
- data/ext/test.c +42 -0
- data/geoipdb.gemspec +71 -0
- data/lib/geoipdb.rb +0 -0
- data/sample_data/cities.csv +7 -0
- data/sample_data/citiess_corrupt.csv +8 -0
- data/sample_data/ip_ranges.csv +11 -0
- data/sample_data/ip_ranges_corrupt.csv +20 -0
- data/spec/geoipdb_spec.rb +37 -0
- data/spec/spec_helper.rb +12 -0
- metadata +150 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.1.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.1"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Eugen Martin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# geoipdb: fast (in memory!) geo location db.
|
2
|
+
|
3
|
+
Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.
|
4
|
+
|
5
|
+
* Returns a GeoLocation to a given IP.
|
6
|
+
* Reads Data from CSV-Files and uses internal binary caching.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
db = GeoIpDb.init "city_codes.csv", "ip_city.txt", "ip_city.cache"
|
11
|
+
location_hash = db.city_by_ip("178.0.0.1")
|
12
|
+
# => {"name"=>"eschborn", "country"=>"deu", "lng"=>8.55, "lat"=>50.133333}
|
13
|
+
|
14
|
+
== Copyright
|
15
|
+
|
16
|
+
Copyright (c) 2010 Eugen Martin. See LICENSE.txt for
|
17
|
+
further details.
|
18
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "geoipdb"
|
16
|
+
gem.homepage = "http://github.com/olgen/geoipdb"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.}
|
19
|
+
gem.description = %Q{Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.}
|
20
|
+
gem.email = "eugeniusmartinus@googlemail.com"
|
21
|
+
gem.authors = ["Eugen Martin"]
|
22
|
+
gem.extensions = ['ext/extconf.rb']
|
23
|
+
gem.require_path = "ext"
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rspec/core'
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
30
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
34
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
35
|
+
spec.rcov = true
|
36
|
+
end
|
37
|
+
|
38
|
+
task :default => :spec
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "geoipdb #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.4
|
data/ext/build.sh
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gcc -Wall -o test test.c ipdb.c ipdb.h
|
data/ext/extconf.rb
ADDED
data/ext/geoipdb.c
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#include "ipdb.h"
|
2
|
+
|
3
|
+
#include "ruby.h"
|
4
|
+
|
5
|
+
/**
|
6
|
+
Ruby Wrapper
|
7
|
+
*/
|
8
|
+
|
9
|
+
typedef struct geipdb {
|
10
|
+
IPDB *db;
|
11
|
+
} geoipdb;
|
12
|
+
|
13
|
+
static VALUE cGeoIpDb;
|
14
|
+
|
15
|
+
VALUE method_hello_world(VALUE self);
|
16
|
+
|
17
|
+
// free the memory used by the db, called by the Ruby-GC
|
18
|
+
void geoipdb_free(geoipdb *gi) {
|
19
|
+
printf("Freeing memory for the GeoIpDb.. \n");
|
20
|
+
if(gi == NULL)
|
21
|
+
return;
|
22
|
+
if (gi->db != NULL){
|
23
|
+
if(gi->db->cities != NULL ){
|
24
|
+
printf("..freeing cities \n");
|
25
|
+
free(gi->db->cities);
|
26
|
+
gi->db->cities = NULL;
|
27
|
+
}
|
28
|
+
if(gi->db->ranges != NULL ){
|
29
|
+
printf("..freeing ranges \n");
|
30
|
+
free(gi->db->ranges);
|
31
|
+
gi->db->ranges = NULL;
|
32
|
+
}
|
33
|
+
if(gi->db != NULL){
|
34
|
+
printf("..freeing ipdb \n");
|
35
|
+
free(gi->db);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
|
41
|
+
VALUE ipdb_init(VALUE self, VALUE cities_file_name, VALUE ranges_file_name, VALUE cache_file_name) {
|
42
|
+
geoipdb *gi;
|
43
|
+
|
44
|
+
Check_Type(cities_file_name, T_STRING);
|
45
|
+
Check_Type(ranges_file_name, T_STRING);
|
46
|
+
Check_Type(cache_file_name, T_STRING);
|
47
|
+
|
48
|
+
char *cities_csv_file = RSTRING(cities_file_name)->ptr;
|
49
|
+
char *ranges_csv_file = RSTRING(ranges_file_name)->ptr;
|
50
|
+
char *cache_file = RSTRING(cache_file_name)->ptr;
|
51
|
+
|
52
|
+
gi = malloc(sizeof(geoipdb));
|
53
|
+
|
54
|
+
gi->db= init_db(cities_csv_file, ranges_csv_file, cache_file);
|
55
|
+
|
56
|
+
if(gi->db == NULL)
|
57
|
+
{
|
58
|
+
if(DEBUG)
|
59
|
+
printf("Could not init DB!\n");
|
60
|
+
/*
|
61
|
+
TODO: Add geoipdb_free in this case.. though not important for production...
|
62
|
+
*/
|
63
|
+
return Qnil;
|
64
|
+
}else{
|
65
|
+
if(DEBUG)
|
66
|
+
printf("\nDB Init completed!\n");
|
67
|
+
return(Data_Wrap_Struct(cGeoIpDb, 0, geoipdb_free, gi));
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
VALUE city_to_hash(City * city){
|
73
|
+
VALUE hash = rb_hash_new();
|
74
|
+
// return a ruby-hash with the needed data
|
75
|
+
rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(city->name));
|
76
|
+
rb_hash_aset(hash, rb_str_new2("country"), rb_str_new2(city->country_iso2));
|
77
|
+
rb_hash_aset(hash, rb_str_new2("lat"), rb_float_new(city->lat));
|
78
|
+
rb_hash_aset(hash, rb_str_new2("lng"), rb_float_new(city->lng));
|
79
|
+
rb_hash_aset(hash, rb_str_new2("city_code"), INT2FIX(city->city_code));
|
80
|
+
return hash;
|
81
|
+
}
|
82
|
+
|
83
|
+
VALUE ipdb_city_by_ip(VALUE self, VALUE ip_string){
|
84
|
+
char *ip = RSTRING(ip_string)->ptr;
|
85
|
+
geoipdb *gi;
|
86
|
+
|
87
|
+
Data_Get_Struct(self, geoipdb, gi);
|
88
|
+
City * city = city_by_ip(gi->db, ip);
|
89
|
+
if(city == NULL)
|
90
|
+
{
|
91
|
+
// printf("Could not find city for ip: %s\n", ip);
|
92
|
+
return Qnil;
|
93
|
+
}else{
|
94
|
+
// printf("Found city for ip: %s\n", ip);
|
95
|
+
// print_city(city);
|
96
|
+
return city_to_hash(city);
|
97
|
+
// create a hash to return to ruby:
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
void Init_geoipdb(void)
|
104
|
+
{
|
105
|
+
//Erstelle die Klasse cGeoIpDb als Subklasse von Object
|
106
|
+
cGeoIpDb = rb_define_class( "GeoIpDb", rb_cObject);
|
107
|
+
rb_define_singleton_method( cGeoIpDb, "init", ipdb_init, 3);
|
108
|
+
rb_define_method( cGeoIpDb, "city_by_ip", ipdb_city_by_ip, 1);
|
109
|
+
}
|
data/ext/ipdb.c
ADDED
@@ -0,0 +1,604 @@
|
|
1
|
+
#include "ipdb.h"
|
2
|
+
|
3
|
+
#include <search.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <string.h>
|
6
|
+
#include <stdlib.h>
|
7
|
+
#include <sys/time.h>
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
const char country_iso2_codes[253][3] = { "--","ap","eu","ad","ae","af","ag","ai","al","am","an",
|
13
|
+
"ao","aq","ar","as","at","au","aw","az","ba","bb",
|
14
|
+
"bd","be","bf","bg","bh","bi","bj","bm","bn","bo",
|
15
|
+
"br","bs","bt","bv","bw","by","bz","ca","cc","cd",
|
16
|
+
"cf","cg","ch","ci","ck","cl","cm","cn","co","cr",
|
17
|
+
"cu","cv","cx","cy","cz","de","dj","dk","dm","do",
|
18
|
+
"dz","ec","ee","eg","eh","er","es","et","fi","fj",
|
19
|
+
"fk","fm","fo","fr","fx","ga","gb","gd","ge","gf",
|
20
|
+
"gh","gi","gl","gm","gn","gp","gq","gr","gs","gt",
|
21
|
+
"gu","gw","gy","hk","hm","hn","hr","ht","hu","id",
|
22
|
+
"ie","il","in","io","iq","ir","is","it","jm","jo",
|
23
|
+
"jp","ke","kg","kh","ki","km","kn","kp","kr","kw",
|
24
|
+
"ky","kz","la","lb","lc","li","lk","lr","ls","lt",
|
25
|
+
"lu","lv","ly","ma","mc","md","mg","mh","mk","ml",
|
26
|
+
"mm","mn","mo","mp","mq","mr","ms","mt","mu","mv",
|
27
|
+
"mw","mx","my","mz","na","nc","ne","nf","ng","ni",
|
28
|
+
"nl","no","np","nr","nu","nz","om","pa","pe","pf",
|
29
|
+
"pg","ph","pk","pl","pm","pn","pr","ps","pt","pw",
|
30
|
+
"py","qa","re","ro","ru","rw","sa","sb","sc","sd",
|
31
|
+
"se","sg","sh","si","sj","sk","sl","sm","sn","so",
|
32
|
+
"sr","st","sv","sy","sz","tc","td","tf","tg","th",
|
33
|
+
"tj","tk","tm","tn","to","tl","tr","tt","tv","tw",
|
34
|
+
"tz","ua","ug","um","us","uy","uz","va","vc","ve",
|
35
|
+
"vg","vi","vn","vu","wf","ws","ye","yt","rs","za",
|
36
|
+
"zm","me","zw","a1","a2","o1","ax","gg","im","je",
|
37
|
+
"bl","mf"};
|
38
|
+
|
39
|
+
static const unsigned num_countries = (unsigned)(sizeof(country_iso2_codes)/sizeof(country_iso2_codes[0]));
|
40
|
+
|
41
|
+
const char country_iso3_codes[253][4] = { "--","ap","eu","and","are","afg","atg","aia","alb","arm","ant",
|
42
|
+
"ago","aq","arg","asm","aut","aus","abw","aze","bih","brb",
|
43
|
+
"bgd","bel","bfa","bgr","bhr","bdi","ben","bmu","brn","bol",
|
44
|
+
"bra","bhs","btn","bv","bwa","blr","blz","can","cc","cod",
|
45
|
+
"caf","cog","che","civ","cok","chl","cmr","chn","col","cri",
|
46
|
+
"cub","cpv","cx","cyp","cze","deu","dji","dnk","dma","dom",
|
47
|
+
"dza","ecu","est","egy","esh","eri","esp","eth","fin","fji",
|
48
|
+
"flk","fsm","fro","fra","fx","gab","gbr","grd","geo","guf",
|
49
|
+
"gha","gib","grl","gmb","gin","glp","gnq","grc","gs","gtm",
|
50
|
+
"gum","gnb","guy","hkg","hm","hnd","hrv","hti","hun","idn",
|
51
|
+
"irl","isr","ind","io","irq","irn","isl","ita","jam","jor",
|
52
|
+
"jpn","ken","kgz","khm","kir","com","kna","prk","kor","kwt",
|
53
|
+
"cym","kaz","lao","lbn","lca","lie","lka","lbr","lso","ltu",
|
54
|
+
"lux","lva","lby","mar","mco","mda","mdg","mhl","mkd","mli",
|
55
|
+
"mmr","mng","mac","mnp","mtq","mrt","msr","mlt","mus","mdv",
|
56
|
+
"mwi","mex","mys","moz","nam","ncl","ner","nfk","nga","nic",
|
57
|
+
"nld","nor","npl","nru","niu","nzl","omn","pan","per","pyf",
|
58
|
+
"png","phl","pak","pol","spm","pcn","pri","pse","prt","plw",
|
59
|
+
"pry","qat","reu","rou","rus","rwa","sau","slb","syc","sdn",
|
60
|
+
"swe","sgp","shn","svn","sjm","svk","sle","smr","sen","som",
|
61
|
+
"sur","stp","slv","syr","swz","tca","tcd","tf","tgo","tha",
|
62
|
+
"tjk","tkl","tkm","tun","ton","tls","tur","tto","tuv","twn",
|
63
|
+
"tza","ukr","uga","um","usa","ury","uzb","vat","vct","ven",
|
64
|
+
"vgb","vir","vnm","vut","wlf","wsm","yem","yt","srb","zaf",
|
65
|
+
"zmb","mne","zwe","a1","a2","o1","ala","ggy","imn","jey",
|
66
|
+
"blm","maf"};
|
67
|
+
|
68
|
+
|
69
|
+
void
|
70
|
+
print_range(const IpRange* e){
|
71
|
+
printf( "from: %lu, to:%lu ->City-idx: %i \n",e->from, e->to,e->city_index );
|
72
|
+
}
|
73
|
+
|
74
|
+
void
|
75
|
+
print_ranges(IPDB * db){
|
76
|
+
int i;
|
77
|
+
for(i = 0; i < db->ranges_count; ++i)
|
78
|
+
{
|
79
|
+
print_range(&(db->ranges[i]));
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
void
|
84
|
+
print_city(const City * e){
|
85
|
+
if(e == NULL)
|
86
|
+
{
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
printf( "City: code:%i, name:%s, country: %s, lat: %10.7f, lng: %10.7f \n",e->city_code, e->name, e->country_iso3, e->lat, e->lng );
|
90
|
+
}
|
91
|
+
|
92
|
+
void
|
93
|
+
print_cities(IPDB * db){
|
94
|
+
int i;
|
95
|
+
for(i = 0; i < db->cities_count; ++i)
|
96
|
+
{
|
97
|
+
print_city(&(db->cities[i]));
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
void print_stats(IPDB * db){
|
104
|
+
printf("DB STATS: \n");
|
105
|
+
printf("\tCities: %i\n", db->cities_count);
|
106
|
+
printf("\tRanges: %i\n", db->ranges_count);
|
107
|
+
}
|
108
|
+
|
109
|
+
double
|
110
|
+
get_time(struct timeval *tim){
|
111
|
+
gettimeofday(tim, NULL);
|
112
|
+
return tim->tv_sec+(tim->tv_usec/1000000.0);
|
113
|
+
}
|
114
|
+
|
115
|
+
|
116
|
+
unsigned long
|
117
|
+
ip_to_int(const char *addr){
|
118
|
+
unsigned int c, octet, t;
|
119
|
+
unsigned long ipnum;
|
120
|
+
int i = 3;
|
121
|
+
|
122
|
+
octet = ipnum = 0;
|
123
|
+
while ((c = *addr++)) {
|
124
|
+
if (c == '.') {
|
125
|
+
if (octet > 255)
|
126
|
+
return 0;
|
127
|
+
ipnum <<= 8;
|
128
|
+
ipnum += octet;
|
129
|
+
i--;
|
130
|
+
octet = 0;
|
131
|
+
} else {
|
132
|
+
t = octet;
|
133
|
+
octet <<= 3;
|
134
|
+
octet += t;
|
135
|
+
octet += t;
|
136
|
+
c -= '0';
|
137
|
+
if (c > 9)
|
138
|
+
return 0;
|
139
|
+
octet += c;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
if ((octet > 255) || (i != 0))
|
143
|
+
return 0;
|
144
|
+
ipnum <<= 8;
|
145
|
+
return ipnum + octet;
|
146
|
+
}
|
147
|
+
|
148
|
+
|
149
|
+
// Function to compare
|
150
|
+
// - either two ip-ranges: i.e.: a(from...to) <=> b(from...to)
|
151
|
+
// - or a ip(i.e. range without to) and an ip-range: i.e. a(from...NULL) <=> b(from...to); a(from...to) <=> b(from ... NULL)
|
152
|
+
int compare_ranges(const void *fa, const void *fb) {
|
153
|
+
if(fa == NULL)
|
154
|
+
{
|
155
|
+
printf("FA IS NULL!!!\n");
|
156
|
+
return 0;
|
157
|
+
}
|
158
|
+
if(fb == NULL)
|
159
|
+
{
|
160
|
+
printf("FB IS NULL!!!\n");
|
161
|
+
return 0;
|
162
|
+
}
|
163
|
+
|
164
|
+
|
165
|
+
const IpRange *a = (IpRange *) fa;
|
166
|
+
const IpRange *b = (IpRange *) fb;
|
167
|
+
|
168
|
+
// printf("\tComparing: a:");
|
169
|
+
// print_range(a);
|
170
|
+
// printf(" with b:");
|
171
|
+
// print_range(b);
|
172
|
+
// printf("\n");
|
173
|
+
|
174
|
+
if(a->from>0 && a->to>0 && b->from>0 && b->to>0){ //regular case: both entries are ranges
|
175
|
+
if(a->to < b->from) {
|
176
|
+
return -1;
|
177
|
+
}else if(a->from > b->to){
|
178
|
+
return +1;
|
179
|
+
}else{
|
180
|
+
return 0;
|
181
|
+
}
|
182
|
+
}else if(a->to == 0 && b->to>0){//a is a search_object
|
183
|
+
if(a->from < b->from) {
|
184
|
+
return -1;
|
185
|
+
}else if(a->from > b->to){
|
186
|
+
return +1;
|
187
|
+
}else{
|
188
|
+
return 0;
|
189
|
+
}
|
190
|
+
}else if(b->to == 0 && a->to>0){//b is a search_object
|
191
|
+
if(b->from < a->from){
|
192
|
+
return -1;
|
193
|
+
}else if(b->from > a->to){
|
194
|
+
return +1;
|
195
|
+
}else{
|
196
|
+
return 0;
|
197
|
+
}
|
198
|
+
}else if(a->to == 0 && b->to == 0){ //both are search objects - this should not happen!
|
199
|
+
return a->from - b->from;
|
200
|
+
}
|
201
|
+
return 0;
|
202
|
+
}
|
203
|
+
|
204
|
+
|
205
|
+
int
|
206
|
+
compare_cities(const void *a, const void *b){
|
207
|
+
const City city_a = *(City*)a;
|
208
|
+
const City city_b = * (City*) b;
|
209
|
+
// sort cities by city_code
|
210
|
+
return city_a.city_code - city_b.city_code;
|
211
|
+
}
|
212
|
+
|
213
|
+
void
|
214
|
+
sort_cities(IPDB * db){
|
215
|
+
if(DEBUG)
|
216
|
+
printf("Sorting %i Cities in db...\n", db->cities_count);
|
217
|
+
|
218
|
+
struct timeval tim;
|
219
|
+
double t1 = get_time(&tim);
|
220
|
+
|
221
|
+
qsort(db->cities,db->cities_count,sizeof(City), compare_cities);
|
222
|
+
if(DEBUG)
|
223
|
+
printf("\n Sorting cities needed %.6lf seconds\n", get_time(&tim)-t1);
|
224
|
+
}
|
225
|
+
|
226
|
+
|
227
|
+
int // returns a city-inde
|
228
|
+
city_index_by_code(IPDB * db, uint16 city_code){
|
229
|
+
City *search, *result;
|
230
|
+
search = malloc(sizeof(City));
|
231
|
+
search->city_code = city_code;
|
232
|
+
result = (City*) bsearch(search, db->cities, db->cities_count, sizeof(City), compare_cities);
|
233
|
+
|
234
|
+
if(search != NULL)
|
235
|
+
free(search);
|
236
|
+
|
237
|
+
if(result == NULL)
|
238
|
+
{
|
239
|
+
if(DEBUG)
|
240
|
+
printf("Could not find searched city with code: %i \n", city_code);
|
241
|
+
return -1;
|
242
|
+
}else{
|
243
|
+
// printf("Found result: \t");
|
244
|
+
// print_city(result);
|
245
|
+
int index;
|
246
|
+
index = (result - db->cities);
|
247
|
+
// printf("Found index: %i\n", index);
|
248
|
+
return index;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
|
253
|
+
City *
|
254
|
+
city_by_ip(IPDB *db, char *ip){
|
255
|
+
IpRange * search, *result;
|
256
|
+
search = (IpRange *)malloc(sizeof(IpRange));
|
257
|
+
void * res;
|
258
|
+
|
259
|
+
// print_stats(db);
|
260
|
+
|
261
|
+
if(db == NULL)
|
262
|
+
{
|
263
|
+
printf("ERROR: DB ist NULL! ");
|
264
|
+
return NULL;
|
265
|
+
}
|
266
|
+
|
267
|
+
if(db->ranges_count == 0)
|
268
|
+
{
|
269
|
+
printf("ERROR: DB has no Ranges Data. Can not search!\n");
|
270
|
+
return NULL;
|
271
|
+
}
|
272
|
+
|
273
|
+
search->from = ip_to_int(ip);
|
274
|
+
search->to=0;
|
275
|
+
search->city_index = 0;
|
276
|
+
if(DEBUG)
|
277
|
+
printf("Searching for: ip=%s, ipnum=%lu \n", ip, search->from);
|
278
|
+
// result = (IpRange*) bsearch(search, db->ranges, db->ranges_count-2, sizeof(IpRange), compare_ranges);
|
279
|
+
res = bsearch(search, db->ranges, db->ranges_count, sizeof(IpRange), compare_ranges);
|
280
|
+
if(search != NULL)
|
281
|
+
free(search);
|
282
|
+
|
283
|
+
if(res == NULL)
|
284
|
+
{
|
285
|
+
if(DEBUG)
|
286
|
+
printf("ERROR: Could not find the IP: %s! THIS SHOULD NOT HAPPEN!\n", ip);
|
287
|
+
return NULL;
|
288
|
+
}else{
|
289
|
+
result = (IpRange*) res;
|
290
|
+
if(DEBUG){
|
291
|
+
printf("Found Range: \t");
|
292
|
+
print_range(result);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
if(db->cities_count == 0)
|
297
|
+
{
|
298
|
+
if(DEBUG)
|
299
|
+
printf("ERROR: DB has no City Data. Can not search!\n");
|
300
|
+
return NULL;
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
if(result->city_index >0 && result->city_index < db->cities_count)
|
305
|
+
{
|
306
|
+
// address the city directly via the array-idx
|
307
|
+
return &(db->cities[result->city_index]);
|
308
|
+
} else {
|
309
|
+
if(DEBUG)
|
310
|
+
printf("ERROR: Could not find city with index: %i - THIS SHOULD NOT HAPPEN!\n", result->city_index);
|
311
|
+
return NULL;
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
|
316
|
+
// read ip-ranges from csv file, of format:
|
317
|
+
// from_ip|to_ip|city_code
|
318
|
+
void
|
319
|
+
read_ranges_csv(IPDB * db){
|
320
|
+
struct timeval tim;
|
321
|
+
double t1 = get_time(&tim);
|
322
|
+
|
323
|
+
db->ranges = malloc(sizeof(IpRange) * db->max_ranges_count);
|
324
|
+
|
325
|
+
if(DEBUG)
|
326
|
+
printf("Parsing RANGES-CSV-file: %s\n", db->ranges_csv_file);
|
327
|
+
FILE * f = fopen(db->ranges_csv_file, "rt");
|
328
|
+
if(f == NULL)
|
329
|
+
{
|
330
|
+
if(DEBUG)
|
331
|
+
printf("Could not open the CSV-file: %s", db->ranges_csv_file);
|
332
|
+
return;
|
333
|
+
}
|
334
|
+
char line[256];
|
335
|
+
char* from = NULL;
|
336
|
+
char* to = NULL;
|
337
|
+
char* city_code = NULL;
|
338
|
+
int invalid_cities_count = 0;
|
339
|
+
int city_index;
|
340
|
+
|
341
|
+
IpRange* entry;
|
342
|
+
db->ranges_count = 0;
|
343
|
+
while (fgets(line,sizeof(line),f) && db->ranges_count < db->max_ranges_count){
|
344
|
+
if(DEBUG && db->ranges_count % 1000000 == 0)
|
345
|
+
printf("Worked lines: %i\n", db->ranges_count);
|
346
|
+
|
347
|
+
from = strtok(line, RANGES_DELIM);
|
348
|
+
to = strtok(NULL, RANGES_DELIM);
|
349
|
+
city_code = strtok(NULL, RANGES_DELIM);
|
350
|
+
city_index = city_index_by_code(db, atoi(city_code));
|
351
|
+
if(city_index < 0)
|
352
|
+
{
|
353
|
+
if(DEBUG)
|
354
|
+
printf("Could not find city for code: %i", atoi(city_code));
|
355
|
+
invalid_cities_count ++;
|
356
|
+
continue;
|
357
|
+
}else{
|
358
|
+
entry = &(db->ranges[db->ranges_count]);
|
359
|
+
|
360
|
+
entry->from = ip_to_int(from);
|
361
|
+
entry->to = ip_to_int(to);
|
362
|
+
// entry->city_code = atoi(city_code);
|
363
|
+
|
364
|
+
entry->city_index = city_index;
|
365
|
+
// printf("Line: %s", line);
|
366
|
+
// printf("from: %u,to: %u, city_code:%i \n",entry->from,entry->to,entry->city_code);
|
367
|
+
// db->ranges[i] = *entry;
|
368
|
+
// printf("working record nr: %li\n", db->ranges_count);
|
369
|
+
db->ranges_count++;
|
370
|
+
}
|
371
|
+
}
|
372
|
+
if(invalid_cities_count)
|
373
|
+
{
|
374
|
+
printf("Found invalid cities: %i", invalid_cities_count);
|
375
|
+
}
|
376
|
+
printf("\n Parsing of %i records needed %.6lf seconds\n", db->ranges_count, get_time(&tim)-t1);
|
377
|
+
}
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
//translate country iso3 to iso2
|
382
|
+
char *
|
383
|
+
iso2_code(char* iso3){
|
384
|
+
int i = 0;
|
385
|
+
for( i = 0; i < num_countries; i++)
|
386
|
+
{
|
387
|
+
// printf("Trying cmp of %s and %s...", iso3, country_iso3_codes[i]);
|
388
|
+
if( strcmp(country_iso3_codes[i],iso3)==0)
|
389
|
+
{
|
390
|
+
return (char*) country_iso2_codes[i];
|
391
|
+
}
|
392
|
+
}
|
393
|
+
// printf("Could not find iso2 code for iso3: %s, using: '%s' \n", iso3, country_iso2_codes[0]);
|
394
|
+
return (char*) country_iso2_codes[0];
|
395
|
+
}
|
396
|
+
|
397
|
+
//read city-data from csv-file of format:
|
398
|
+
// COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
|
399
|
+
void
|
400
|
+
read_cities_csv(IPDB * db){
|
401
|
+
struct timeval tim;
|
402
|
+
double t1 = get_time(&tim);
|
403
|
+
|
404
|
+
db->cities_count = 0;
|
405
|
+
db->cities = malloc(sizeof(City) * db->max_cities_count);
|
406
|
+
|
407
|
+
if(DEBUG)
|
408
|
+
printf("Parsing Cities-CSV-file: %s\n", db->cities_csv_file);
|
409
|
+
FILE * f = fopen(db->cities_csv_file, "rt");
|
410
|
+
if(f == NULL)
|
411
|
+
{
|
412
|
+
if(DEBUG)
|
413
|
+
printf("Could not open the Cities-CSV-file: %s", db->cities_csv_file);
|
414
|
+
return;
|
415
|
+
}
|
416
|
+
char line[256];
|
417
|
+
char *country, *region, *name,*metro_code,*city_code,*lat,*lng ;
|
418
|
+
int i = 0;
|
419
|
+
City* entry;
|
420
|
+
|
421
|
+
while (fgets(line,sizeof(line),f) && db->cities_count < db->max_cities_count){
|
422
|
+
i++;
|
423
|
+
if(i == 1)
|
424
|
+
continue;//skip the header
|
425
|
+
|
426
|
+
if(DEBUG && i % 1000000 == 0)
|
427
|
+
{
|
428
|
+
printf("Worked lines: %i\n", i);
|
429
|
+
}
|
430
|
+
// printf("Line: %s", line);
|
431
|
+
// COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
|
432
|
+
country = strtok(line, CITIES_DELIM);
|
433
|
+
region = strtok(NULL, CITIES_DELIM);
|
434
|
+
name = strtok(NULL, CITIES_DELIM);
|
435
|
+
metro_code = strtok(NULL, CITIES_DELIM);
|
436
|
+
city_code = strtok(NULL, CITIES_DELIM);
|
437
|
+
lat = strtok(NULL, CITIES_DELIM);
|
438
|
+
lng = strtok(NULL, CITIES_DELIM);
|
439
|
+
|
440
|
+
entry = &(db->cities[db->cities_count]);
|
441
|
+
|
442
|
+
strncpy(entry->country_iso3, country, strlen(country));
|
443
|
+
|
444
|
+
// entry->country_iso2 = iso2_code(entry->country_iso3);
|
445
|
+
strncpy(entry->country_iso2, iso2_code(country), 2);
|
446
|
+
strncpy(entry->name, name, strlen(name));
|
447
|
+
|
448
|
+
entry->city_code = atoi(city_code);
|
449
|
+
entry->lat = atof(lat);
|
450
|
+
entry->lng = atof(lng);
|
451
|
+
db->cities_count++;
|
452
|
+
}
|
453
|
+
if(DEBUG)
|
454
|
+
printf("\n Parsing of %i records needed %.6lf seconds\n", db->cities_count, get_time(&tim)-t1);
|
455
|
+
}
|
456
|
+
|
457
|
+
|
458
|
+
|
459
|
+
/**
|
460
|
+
cache-file is an exact binary copy of the ranges+cities-arrays from memory,
|
461
|
+
the layout goes like this:
|
462
|
+
db->cities_count [4 Bytes]
|
463
|
+
db->ranges_count [4 Bytes]
|
464
|
+
|
465
|
+
db->cities [sizeof(City)=24 x db->ranges_count Bytes]
|
466
|
+
db->ranges [sizeof(IpRange)=24 x db->ranges_count Bytes]
|
467
|
+
*/
|
468
|
+
void
|
469
|
+
write_cache_file(IPDB * db){
|
470
|
+
struct timeval tim;
|
471
|
+
double t1 = get_time(&tim);
|
472
|
+
int objects_written;
|
473
|
+
|
474
|
+
FILE * f;
|
475
|
+
f = fopen(db->cache_file_name, "w");
|
476
|
+
if(f==NULL){
|
477
|
+
if(DEBUG)
|
478
|
+
printf("Could not open Cache-File: %s", db->cache_file_name);
|
479
|
+
return;
|
480
|
+
}
|
481
|
+
if(DEBUG){
|
482
|
+
printf("Dumping %i records to cache-file: %s\n\n", db->ranges_count, db->cache_file_name);
|
483
|
+
|
484
|
+
//write the record length at file header
|
485
|
+
printf("Writing DB-Header of length: %li\n",sizeof(db->ranges_count));
|
486
|
+
|
487
|
+
printf("RecordLength: %li\n",sizeof(IpRange));
|
488
|
+
printf("FieldLength: %li\n",sizeof(db->ranges[0].from));
|
489
|
+
}
|
490
|
+
//write the header: i.e.: numbers of records
|
491
|
+
fwrite(&(db->cities_count), sizeof(db->cities_count),1,f);
|
492
|
+
fwrite(&(db->ranges_count), sizeof(db->ranges_count),1,f);
|
493
|
+
|
494
|
+
if(DEBUG)
|
495
|
+
printf("Writing Contents with %i cities, a %li bytes each, should = %li \n", db->cities_count, sizeof(City), db->cities_count * sizeof(City));
|
496
|
+
//write the actual data: all the ranges-array-buffer:
|
497
|
+
objects_written = fwrite(db->cities, sizeof(City), db->cities_count, f);
|
498
|
+
if(DEBUG)
|
499
|
+
printf("Writing Contents with %i ranges, a %li bytes each, should = %li \n", db->ranges_count, sizeof(IpRange), db->ranges_count * sizeof(IpRange));
|
500
|
+
//write the actual data: all the ranges-array-buffer:
|
501
|
+
objects_written += fwrite(db->ranges, sizeof(IpRange), db->ranges_count, f);
|
502
|
+
|
503
|
+
|
504
|
+
fclose(f);
|
505
|
+
if(DEBUG)
|
506
|
+
printf("\n Writing CacheFile of %i objects needed %.6lf seconds\n", objects_written, get_time(&tim)-t1);
|
507
|
+
}
|
508
|
+
|
509
|
+
int
|
510
|
+
read_cache_file(IPDB * db){
|
511
|
+
struct timeval tim;
|
512
|
+
double t1 = get_time(&tim);
|
513
|
+
FILE * f;
|
514
|
+
f = fopen(db->cache_file_name, "r");
|
515
|
+
if(f==NULL){
|
516
|
+
if(DEBUG)
|
517
|
+
printf("Could not open Cache-File: %s", db->cache_file_name);
|
518
|
+
return 0;
|
519
|
+
}
|
520
|
+
int cities_header_read = fread(&(db->cities_count), sizeof(db->cities_count),1,f);
|
521
|
+
int ranges_header_read = fread(&(db->ranges_count), sizeof(db->ranges_count),1,f);
|
522
|
+
|
523
|
+
|
524
|
+
if(cities_header_read == 0 || ranges_header_read == 0 || db->cities_count == 0 || db->ranges_count ==0 )
|
525
|
+
{
|
526
|
+
printf("Could not read Cities-Header from Cache-File: %s", db->cache_file_name);
|
527
|
+
return 0;
|
528
|
+
}
|
529
|
+
if(DEBUG)
|
530
|
+
printf("Reading DB-Header from Cache-File: %s, with %i cities and %i ranges\n",db->cache_file_name, db->cities_count, db->ranges_count);
|
531
|
+
|
532
|
+
int objects_read = 0;
|
533
|
+
if(DEBUG)
|
534
|
+
printf("Allocating: %lu for cities-array \n", sizeof(City)*(db->cities_count));
|
535
|
+
db->cities = malloc(sizeof(City) * db->cities_count);
|
536
|
+
objects_read += fread(db->cities, sizeof(City),db->cities_count,f);
|
537
|
+
if(DEBUG)
|
538
|
+
printf("Allocating: %lu for ranges-array \n", sizeof(IpRange)*(db->ranges_count));
|
539
|
+
db->ranges = malloc(sizeof(IpRange) * db->ranges_count);
|
540
|
+
objects_read += fread(db->ranges, sizeof(IpRange),db->ranges_count,f);
|
541
|
+
|
542
|
+
|
543
|
+
fclose(f);
|
544
|
+
if(DEBUG)
|
545
|
+
printf("Reading cacheFile of %i objects needed %.6lf seconds\n", objects_read, get_time(&tim)-t1);
|
546
|
+
return objects_read;
|
547
|
+
}
|
548
|
+
|
549
|
+
void
|
550
|
+
benchmark_search(IPDB * db,int count){
|
551
|
+
printf("(Naiv) benchmark of the City-Search-Function with %i counts \n", count);
|
552
|
+
struct timeval tim;
|
553
|
+
double t1 = get_time(&tim);
|
554
|
+
int i;
|
555
|
+
City * city;
|
556
|
+
|
557
|
+
for(i=0;i<count; i++){
|
558
|
+
city = city_by_ip(db, "278.50.47.0");
|
559
|
+
}
|
560
|
+
double delta = get_time(&tim)-t1;
|
561
|
+
|
562
|
+
printf("\n\nSearch: %.6lf seconds elapsed, i.e. %.6lf Ops/Second \n", delta, count / delta);
|
563
|
+
}
|
564
|
+
|
565
|
+
IPDB * init_db(char * cities_csv_file, char * ranges_csv_file, char * cache_file_name){
|
566
|
+
IPDB *db;
|
567
|
+
db = (IPDB*)malloc(sizeof(IPDB));
|
568
|
+
if (db == NULL) //no memory left
|
569
|
+
return NULL;
|
570
|
+
db->cities = NULL;
|
571
|
+
db->ranges = NULL;
|
572
|
+
db->cache_file_name = cache_file_name;
|
573
|
+
|
574
|
+
db->cities_csv_file = cities_csv_file;
|
575
|
+
db->max_cities_count = MAX_CITIES_COUNT;
|
576
|
+
db->ranges_csv_file = ranges_csv_file;
|
577
|
+
db->max_ranges_count = MAX_RANGES_COUNT;
|
578
|
+
|
579
|
+
|
580
|
+
if(USE_CACHE && read_cache_file(db) > 0){
|
581
|
+
if(DEBUG)
|
582
|
+
printf("Loaded DB from Cache-File with %i records \n", db->ranges_count);
|
583
|
+
}else{
|
584
|
+
if(DEBUG)
|
585
|
+
printf("Initializing IPDB from CSV-file: %s \n", db->ranges_csv_file);
|
586
|
+
read_cities_csv(db);
|
587
|
+
// print_cities(db);
|
588
|
+
if(db->cities_count == 0)
|
589
|
+
{
|
590
|
+
return NULL;
|
591
|
+
}
|
592
|
+
sort_cities(db);
|
593
|
+
read_ranges_csv(db);
|
594
|
+
// //TODO: sort ranges
|
595
|
+
if(db!=NULL && db->ranges_count > 0 && USE_CACHE)
|
596
|
+
{
|
597
|
+
if(DEBUG)
|
598
|
+
printf("Got %i records from CSV-file, writing to cache...\n", db->ranges_count);
|
599
|
+
write_cache_file(db);
|
600
|
+
}
|
601
|
+
}
|
602
|
+
return db;
|
603
|
+
}
|
604
|
+
|
data/ext/ipdb.h
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
#ifdef INT_2_BYTES
|
3
|
+
typedef char int8;
|
4
|
+
typedef int int16;
|
5
|
+
typedef unsigned int uint16;
|
6
|
+
typedef long int32;
|
7
|
+
#else
|
8
|
+
typedef char int8;
|
9
|
+
typedef short int16;
|
10
|
+
typedef unsigned short uint16;
|
11
|
+
typedef int int32;
|
12
|
+
#endif
|
13
|
+
|
14
|
+
|
15
|
+
#define RANGES_DELIM "|"
|
16
|
+
#define CITIES_DELIM ","
|
17
|
+
#define MAX_CITIES_COUNT 100000 //Usually we have about 50 000 Cities
|
18
|
+
#define MAX_RANGES_COUNT 10000000 //Usually we have about 6 Mio IP-Ranges
|
19
|
+
|
20
|
+
|
21
|
+
#define USE_CACHE 1
|
22
|
+
#define DEBUG 0
|
23
|
+
|
24
|
+
typedef struct{
|
25
|
+
unsigned long from;
|
26
|
+
unsigned long to;
|
27
|
+
// uint16 city_code; //city codes are not larger than 2**16 = 65536
|
28
|
+
uint16 city_index; //index of the city in the cities-array
|
29
|
+
} IpRange;
|
30
|
+
|
31
|
+
typedef struct{
|
32
|
+
uint16 city_code; //city codes are not larger than 2**16 = 65536
|
33
|
+
char name[32];
|
34
|
+
double lat;
|
35
|
+
double lng;
|
36
|
+
|
37
|
+
char country_iso3[4];
|
38
|
+
char country_iso2[3];
|
39
|
+
} City;
|
40
|
+
|
41
|
+
typedef struct{
|
42
|
+
char *ranges_csv_file; //the CSV-file with ip-ranges-to-city-code-mappings
|
43
|
+
unsigned int max_ranges_count;
|
44
|
+
unsigned int ranges_count;
|
45
|
+
|
46
|
+
char *cities_csv_file;
|
47
|
+
unsigned int cities_count;
|
48
|
+
unsigned int max_cities_count;
|
49
|
+
|
50
|
+
|
51
|
+
char *cache_file_name; // a binary file to store the whole db.....
|
52
|
+
IpRange * ranges;
|
53
|
+
City * cities;
|
54
|
+
|
55
|
+
} IPDB;
|
56
|
+
|
57
|
+
|
58
|
+
// "publicly" visible functions
|
59
|
+
IPDB *
|
60
|
+
init_db(char * cities_csv_file, char * ranges_csv_file, char * cache_file_name);
|
61
|
+
|
62
|
+
City *
|
63
|
+
city_by_ip(IPDB *db, char *ip);
|
64
|
+
|
65
|
+
void
|
66
|
+
print_city(const City * e);
|
67
|
+
|
68
|
+
void
|
69
|
+
benchmark_search(IPDB * db,int count);
|
data/ext/test.c
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#include "ipdb.h"
|
2
|
+
|
3
|
+
#include <search.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <string.h>
|
6
|
+
#include <stdlib.h>
|
7
|
+
#include <sys/time.h>
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
int main() {
|
12
|
+
IPDB * db = init_db("data/cities.csv", "data/ip_ranges.csv", "data/ipdb.cache");
|
13
|
+
// IPDB * db = init_db(NULL, NULL, NULL);
|
14
|
+
|
15
|
+
|
16
|
+
// City * city;
|
17
|
+
//
|
18
|
+
// // city = city_by_ip(db, "1.16.0.0");
|
19
|
+
// int i;
|
20
|
+
int count = 10000000;
|
21
|
+
|
22
|
+
benchmark_search(db, count);
|
23
|
+
|
24
|
+
// for(i = 0; i < count; ++i)
|
25
|
+
// {
|
26
|
+
// if(i % 100000 == 0)
|
27
|
+
// printf("Working: i= %i \n", i);
|
28
|
+
//
|
29
|
+
// city = city_by_ip(db, "91.44.93.35");
|
30
|
+
// }
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
//
|
37
|
+
int * response;
|
38
|
+
scanf("%i",response);
|
39
|
+
|
40
|
+
return 0;
|
41
|
+
|
42
|
+
}
|
data/geoipdb.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{geoipdb}
|
8
|
+
s.version = "0.1.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Eugen Martin"]
|
12
|
+
s.date = %q{2011-01-13}
|
13
|
+
s.description = %q{Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.}
|
14
|
+
s.email = %q{eugeniusmartinus@googlemail.com}
|
15
|
+
s.extensions = ["ext/extconf.rb"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.markdown"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".rspec",
|
23
|
+
"Gemfile",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.markdown",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"ext/build.sh",
|
29
|
+
"ext/extconf.rb",
|
30
|
+
"ext/geoipdb.c",
|
31
|
+
"ext/ipdb.c",
|
32
|
+
"ext/ipdb.h",
|
33
|
+
"ext/test.c",
|
34
|
+
"geoipdb.gemspec",
|
35
|
+
"lib/geoipdb.rb",
|
36
|
+
"spec/geoipdb_spec.rb",
|
37
|
+
"spec/spec_helper.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/olgen/geoipdb}
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["ext"]
|
42
|
+
s.rubygems_version = %q{1.3.7}
|
43
|
+
s.summary = %q{Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.}
|
44
|
+
s.test_files = [
|
45
|
+
"spec/geoipdb_spec.rb",
|
46
|
+
"spec/spec_helper.rb"
|
47
|
+
]
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.1.0"])
|
55
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
56
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
57
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<rspec>, ["~> 2.1.0"])
|
60
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
61
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
62
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rspec>, ["~> 2.1.0"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
68
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
data/lib/geoipdb.rb
ADDED
File without changes
|
@@ -0,0 +1,7 @@
|
|
1
|
+
COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
|
2
|
+
***,***,?,0,7,0,0
|
3
|
+
afg,?,?,0,1,33,1
|
4
|
+
afg,no region,herat,-1,2,34.3452,62.
|
5
|
+
afg,no region,kabul,-1,3,34.5167,69.1833
|
6
|
+
afg,no region,kandahar,-1,4,31.6111,65.702
|
7
|
+
afg,no region,mazar-e sharif,-1,5,36.7041,67.1096
|
@@ -0,0 +1,8 @@
|
|
1
|
+
COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
|
2
|
+
***,***,?,0,7,0,0
|
3
|
+
afg,?,?,0,1,33,1
|
4
|
+
afg,no region,herat,-1,2,34.3452,62.
|
5
|
+
|
6
|
+
afg,no , , , , region,kabul,-1,3,34.5167,69.1833
|
7
|
+
afg,no region,, , kandahar,-1,4,31.6111,65.702
|
8
|
+
afg,no region,maza , ,r-e sharif,-1,5,36.7041,67.1096
|
@@ -0,0 +1,11 @@
|
|
1
|
+
start_ip|end_ip|field 13|
|
2
|
+
0.0.0.0|0.0.0.255|0|
|
3
|
+
0.0.1.0|0.255.255.255|1|
|
4
|
+
1.0.0.0|1.0.0.255|2|
|
5
|
+
1.0.1.0|1.1.0.255|3|
|
6
|
+
1.1.1.0|1.1.1.255|4|
|
7
|
+
1.1.2.0|1.2.2.255|5|
|
8
|
+
1.2.3.0|1.2.3.255|5|
|
9
|
+
1.2.4.0|1.3.255.255|2|
|
10
|
+
1.4.0.0|1.4.0.255|5|
|
11
|
+
1.4.1.0|1.8.255.255|3|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
start_ip|end_ip|field 13|
|
5
|
+
0.0.0.0|0.0.0.255|0|
|
6
|
+
|
7
|
+
0.0.1.0|0.255.255.255|1|
|
8
|
+
|
9
|
+
1.0.0.0|1.0.0.255|2|
|
10
|
+
|
11
|
+
1.0.1.0|1.1.0.255|3|
|
12
|
+
|
13
|
+
asfdasdf asdf asfasdfasdf§$%&/
|
14
|
+
|
15
|
+
1, | | 7 |.1.1.0|1.1.1.255|4|
|
16
|
+
1.1.2.0|1.2.2.255|5|
|
17
|
+
1.2.3.0|1.2.3.255|5|
|
18
|
+
1.2.4.0|1.3.255.255|2|
|
19
|
+
1.4.0.0|1.4.0.255|5|
|
20
|
+
1.4.1.0|1.8.255.255|3|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+'/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe "Geoipdb" do
|
5
|
+
def init_db
|
6
|
+
@db = GeoIpDb.init './sample_data/cities.csv', './sample_data/ip_ranges.csv', @cache_file
|
7
|
+
end
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@cache_file = 'sample_data/ipdb.cache'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should init correctly with sample data and create the cache-file" do
|
14
|
+
init_db
|
15
|
+
@db.should_not be_nil
|
16
|
+
File.exist?(@cache_file).should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sould find the sample cities correcty" do
|
20
|
+
init_db
|
21
|
+
#afg,no region,kabul,-1,3,34.5167,69.1833
|
22
|
+
@db.city_by_ip("1.1.0.254").should == {'city_code'=>3, 'name'=>'kabul', 'country'=>'af', 'lat'=>34.5167, 'lng'=>69.1833}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not throw an exception fault if data is corrupt" do
|
26
|
+
@db = GeoIpDb.init './sample_data/cities_corrupt.csv', './sample_data/ip_ranges_corrupt.csv', @cache_file
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not init a db object if data files are missing" do
|
30
|
+
GeoIpDb.init( './sample_data/bla.csv', './sample_data/blubb.csv', @cache_file ).should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
after :each do
|
35
|
+
File.unlink @cache_file if File.exist? @cache_file
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'ext'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'geoipdb'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geoipdb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Eugen Martin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-14 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :development
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 11
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: 2.1.0
|
34
|
+
requirement: *id001
|
35
|
+
prerelease: false
|
36
|
+
name: rspec
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :development
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 23
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
version: 1.0.0
|
50
|
+
requirement: *id002
|
51
|
+
prerelease: false
|
52
|
+
name: bundler
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
type: :development
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 1
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 5
|
64
|
+
- 1
|
65
|
+
version: 1.5.1
|
66
|
+
requirement: *id003
|
67
|
+
prerelease: false
|
68
|
+
name: jeweler
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
type: :development
|
71
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirement: *id004
|
81
|
+
prerelease: false
|
82
|
+
name: rcov
|
83
|
+
description: Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.
|
84
|
+
email: eugeniusmartinus@googlemail.com
|
85
|
+
executables: []
|
86
|
+
|
87
|
+
extensions:
|
88
|
+
- ext/extconf.rb
|
89
|
+
extra_rdoc_files:
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.markdown
|
92
|
+
files:
|
93
|
+
- .document
|
94
|
+
- .rspec
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.markdown
|
98
|
+
- Rakefile
|
99
|
+
- VERSION
|
100
|
+
- ext/build.sh
|
101
|
+
- ext/extconf.rb
|
102
|
+
- ext/geoipdb.c
|
103
|
+
- ext/ipdb.c
|
104
|
+
- ext/ipdb.h
|
105
|
+
- ext/test.c
|
106
|
+
- geoipdb.gemspec
|
107
|
+
- lib/geoipdb.rb
|
108
|
+
- sample_data/cities.csv
|
109
|
+
- sample_data/citiess_corrupt.csv
|
110
|
+
- sample_data/ip_ranges.csv
|
111
|
+
- sample_data/ip_ranges_corrupt.csv
|
112
|
+
- spec/geoipdb_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
has_rdoc: true
|
115
|
+
homepage: http://github.com/olgen/geoipdb
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
|
121
|
+
require_paths:
|
122
|
+
- ext
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.4.2
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.
|
148
|
+
test_files:
|
149
|
+
- spec/geoipdb_spec.rb
|
150
|
+
- spec/spec_helper.rb
|