ipcat 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.
- data/README.md +28 -0
- data/Rakefile +17 -0
- data/data/datacenters +0 -0
- data/lib/ipcat.rb +70 -0
- data/lib/ipcat/iprange.rb +34 -0
- data/lib/ipcat/version.rb +3 -0
- data/spec/benchmark_spec.rb +32 -0
- data/spec/ipcat_iprange_spec.rb +26 -0
- data/spec/ipcat_spec.rb +24 -0
- data/spec/spec_helper.rb +7 -0
- metadata +123 -0
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# ipcat-ruby
|
2
|
+
|
3
|
+
A ruby port of the [ipcat](https://github.com/client9/ipcat) library to classify IP addresses from known datacenters
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
With bundler:
|
8
|
+
|
9
|
+
# In Gemfile
|
10
|
+
gem 'ipcat'
|
11
|
+
|
12
|
+
Or with rubygems:
|
13
|
+
|
14
|
+
gem install ipcat
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
IPCat.matches?(ip_address)
|
19
|
+
|
20
|
+
It will return an IPCat::IPRange if ip_address is from a known datacenter; nil otherwise.
|
21
|
+
|
22
|
+
For example,
|
23
|
+
|
24
|
+
range = IPCat.matches?('8.18.145.0') # => instance of IPCat::IPRange
|
25
|
+
range.name # => 'Amazon AWS'
|
26
|
+
|
27
|
+
IPCat.matches?('127.0.0.1') # => nil
|
28
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.pattern = "spec/*_spec.rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
task :generate_dataset do
|
12
|
+
|
13
|
+
$:.unshift './lib'
|
14
|
+
require 'ipcat'
|
15
|
+
IPCat.load_csv!
|
16
|
+
File.open("data/datacenters", 'w') {|f| f << Marshal.dump(IPCat.ranges) }
|
17
|
+
end
|
data/data/datacenters
ADDED
Binary file
|
data/lib/ipcat.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
##
|
2
|
+
# IPCat
|
3
|
+
# Ruby lib for https://github.com/client9/ipcat/
|
4
|
+
|
5
|
+
require 'ipaddr'
|
6
|
+
require 'ipcat/iprange'
|
7
|
+
require 'ipcat/version'
|
8
|
+
|
9
|
+
|
10
|
+
class IPCat
|
11
|
+
class << self
|
12
|
+
def matches?(ip)
|
13
|
+
bsearch(ip_to_fixnum(ip))
|
14
|
+
end
|
15
|
+
|
16
|
+
def ip_to_fixnum(ip)
|
17
|
+
Fixnum === ip ? ip : IPAddr.new(ip).to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def ranges
|
21
|
+
@ranges ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset_ranges!(new_ranges = [])
|
25
|
+
@ranges = new_ranges
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_csv!(path='https://raw.github.com/client9/ipcat/master/datacenters.csv')
|
29
|
+
reset_ranges!
|
30
|
+
|
31
|
+
require 'open-uri'
|
32
|
+
open(path).readlines.each do |line|
|
33
|
+
next if line =~/\s*#/ # Skip comments
|
34
|
+
first, last, name, url = line.split(',')
|
35
|
+
self.ranges << IPRange.new(first, last, name, url).freeze
|
36
|
+
end
|
37
|
+
self.ranges.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def load!
|
41
|
+
reset_ranges!
|
42
|
+
# NB: loading an array of marshaled ruby objects takes ~15ms;
|
43
|
+
# versus ~100ms to load a CSV file
|
44
|
+
path = File.join(File.dirname(__FILE__), '..', 'data', 'datacenters')
|
45
|
+
@ranges = Marshal.load(File.read(path))
|
46
|
+
@ranges.each(&:freeze)
|
47
|
+
@ranges.freeze
|
48
|
+
rescue
|
49
|
+
load_csv!
|
50
|
+
end
|
51
|
+
|
52
|
+
# Assume ranges is an array of comparable objects
|
53
|
+
def bsearch(needle, haystack=ranges, first=0, last=ranges.size-1)
|
54
|
+
return nil if last < first # not found, or empty range
|
55
|
+
|
56
|
+
cur = first + (last - first)/2
|
57
|
+
case ranges[cur] <=> needle
|
58
|
+
when -1 # needle is larger than cur value
|
59
|
+
bsearch(needle, haystack, cur+1, last)
|
60
|
+
when 1 # needle is smaller than cur value
|
61
|
+
bsearch(needle, haystack, first, cur-1)
|
62
|
+
when 0
|
63
|
+
ranges[cur]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Load dataset
|
70
|
+
IPCat.load!
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class IPCat
|
2
|
+
class IPRange
|
3
|
+
|
4
|
+
attr_accessor :first, :last, :name, :url
|
5
|
+
|
6
|
+
def initialize(first, last, name=nil, url=nil)
|
7
|
+
@first = IPCat.ip_to_fixnum(first)
|
8
|
+
@last = IPCat.ip_to_fixnum(last)
|
9
|
+
@name, @url = name, url
|
10
|
+
raise ArgumentError.new("first must be <= last") if @first > @last
|
11
|
+
end
|
12
|
+
|
13
|
+
def <=>(obj)
|
14
|
+
case obj
|
15
|
+
when Fixnum
|
16
|
+
compare_with_fixnum(obj)
|
17
|
+
when IPRange
|
18
|
+
# Assume all IPRanges are non-overlapping
|
19
|
+
first <=> obj.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def compare_with_fixnum(i)
|
25
|
+
if first > i
|
26
|
+
1
|
27
|
+
elsif last < i
|
28
|
+
-1
|
29
|
+
else
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'minitest/benchmark'
|
3
|
+
|
4
|
+
describe 'IPCat.bsearch' do
|
5
|
+
|
6
|
+
# Makes +n+ IPRanges
|
7
|
+
def make_ranges(n)
|
8
|
+
ips = (n*2).times.map{ rand(2**32) }.sort
|
9
|
+
ranges = []
|
10
|
+
ips.each_slice(2) do |first, last|
|
11
|
+
ranges << IPCat::IPRange.new(first, last)
|
12
|
+
end
|
13
|
+
|
14
|
+
ranges
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
@ips = 1000.times.map{ rand(2**32) }
|
19
|
+
@ranges = MiniTest::Spec.bench_range.inject({}) {|h, n|
|
20
|
+
h[n] = make_ranges(n)
|
21
|
+
h
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it("should be logarithmic") do
|
26
|
+
assert_performance_logarithmic 0.95 do |n|
|
27
|
+
IPCat.reset_ranges!(@ranges[n])
|
28
|
+
@ips.each { |ip| IPCat.bsearch(ip) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe 'IPCat::IPRange' do
|
4
|
+
attr_accessor :range
|
5
|
+
|
6
|
+
before do
|
7
|
+
IPCat.reset_ranges!
|
8
|
+
first = IPAddr.new('1.2.3.0').to_i
|
9
|
+
last = IPAddr.new('1.2.3.255').to_i
|
10
|
+
@range = IPCat::IPRange.new(first, last, 'example', 'www.example.com')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#initialize' do
|
14
|
+
it "should fail if last < first" do
|
15
|
+
->{ IPCat::IPRange.new(2, 1) }.must_raise ArgumentError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#<=> for fixnums' do
|
20
|
+
it("should match first") { (range <=> range.first).must_equal 0 }
|
21
|
+
it("should match last") { (range <=> range.last).must_equal 0 }
|
22
|
+
it("should match first-1") { (range <=> range.first - 1).must_equal 1 }
|
23
|
+
it("should match last+1") { (range <=> range.last + 1).must_equal -1 }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/spec/ipcat_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe 'IPCat' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
IPCat.reset_ranges!
|
7
|
+
start = IPAddr.new('1.2.3.0').to_i
|
8
|
+
stop = IPAddr.new('1.2.3.255').to_i
|
9
|
+
IPCat.ranges << IPCat::IPRange.new(start, stop, 'example', 'www.example.com')
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#ranges' do
|
13
|
+
it("has a range") { IPCat.ranges.size.must_equal 1 }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#matches?' do
|
17
|
+
it("should match 1.2.3.0") { IPCat.matches?('1.2.3.0').must_be_instance_of IPCat::IPRange }
|
18
|
+
it("should match 1.2.3.1") { IPCat.matches?('1.2.3.1').must_be_instance_of IPCat::IPRange }
|
19
|
+
it("should match 1.2.3.1") { IPCat.matches?('1.2.3.1').must_be_instance_of IPCat::IPRange }
|
20
|
+
it("should not match 1.1.1.1") { IPCat.matches?('1.1.1.1').must_be_nil }
|
21
|
+
it("should not match 2.2.2.2") { IPCat.matches?('2.2.2.2').must_be_nil }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ipcat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Aaron Suggs
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ruby-prof
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: debugger
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.1.3
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.1.3
|
78
|
+
description: ! 'A ruby port of the ipcat library: https://github.com/client9/ipcat/'
|
79
|
+
email: aaron@ktheory.com
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files: []
|
83
|
+
files:
|
84
|
+
- lib/ipcat/iprange.rb
|
85
|
+
- lib/ipcat/version.rb
|
86
|
+
- lib/ipcat.rb
|
87
|
+
- data/datacenters
|
88
|
+
- Rakefile
|
89
|
+
- README.md
|
90
|
+
- spec/benchmark_spec.rb
|
91
|
+
- spec/ipcat_iprange_spec.rb
|
92
|
+
- spec/ipcat_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
homepage: https://github.com/kickstarter/ipcat-ruby
|
95
|
+
licenses: []
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options:
|
98
|
+
- --charset=UTF-8
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.9.3
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.8.24
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: dataset for categorizing IP addresses in ruby
|
119
|
+
test_files:
|
120
|
+
- spec/benchmark_spec.rb
|
121
|
+
- spec/ipcat_iprange_spec.rb
|
122
|
+
- spec/ipcat_spec.rb
|
123
|
+
- spec/spec_helper.rb
|