iprange 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +1 -0
- data/examples/gixlg_importer.rb +54 -0
- data/iprange.gemspec +26 -0
- data/lib/iprange.rb +41 -0
- data/lib/iprange/version.rb +3 -0
- data/nginx/README.md +17 -0
- data/nginx/nginx.conf +25 -0
- data/nginx/range_lookup.lua +52 -0
- data/nginx/redis.lua +466 -0
- data/nginx/reload_nginx.sh +1 -0
- data/nginx/start_nginx.sh +1 -0
- data/spec/iprange_spec.rb +91 -0
- data/spec/spec_helper.rb +1 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4cf4818d4ba6e945d3d11f15edd860b7586bf815
|
4
|
+
data.tar.gz: 10995790fd132aa1bc7100f71b4ddb9f155e75f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0d4ffb3db2965f645f80c92b77fb77b2623cb34c2fbd485426d652cc39678e04d3a173d5dc867825e36613a2bc4c03aeac95c925204ac6bedc79edb318daf273
|
7
|
+
data.tar.gz: f9d801b8bd54dedb193ba169b7aa738bfe1af0fd94fcd10d87135c5359195120eca1c1252664753de46d02b25bbb12035e4bacdf0fb288b1c18548a976bcc35b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Juarez Bochi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# IPRange
|
2
|
+
|
3
|
+
Store IP Ranges in Redis as sorted sets for fast retrieval
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'iprange'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install iprange
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
> redis_config = {host: "127.0.0.1"}
|
22
|
+
> range = IPRange::Range.new(redis_config)
|
23
|
+
> range.add("192.168.0.1/24", some: "data", more: "metadata")
|
24
|
+
> range.find("192.168.0.20")
|
25
|
+
=> {:range=>"192.168.0.1/24", "some"=>"data", "more"=>"metadata"}
|
26
|
+
|
27
|
+
## Notice
|
28
|
+
|
29
|
+
This gem relies on [a Redis fork that implements interval sets](https://github.com/hoxworth/redis/tree/2.6-intervals), as described in this [blog post](http://blog.togo.io/how-to/adding-interval-sets-to-redis/).
|
30
|
+
|
31
|
+
If your intervals do not, you can try the tag v0.0.1, which uses sorted sets instead of inverval sets.
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it ( http://github.com/jbochi/iprange/fork )
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'iprange'
|
4
|
+
require 'mysql2'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
mysql_config = {
|
8
|
+
host: "localhost",
|
9
|
+
database: "looking_glass_db",
|
10
|
+
username: "looking_glass_usr",
|
11
|
+
password: "looking_glass_pwd"
|
12
|
+
}
|
13
|
+
|
14
|
+
redis_config = {}
|
15
|
+
|
16
|
+
LAST_UPDATE_TIME_KEY = 'last_update_time'
|
17
|
+
|
18
|
+
def update(mysql, redis_config)
|
19
|
+
range = IPRange::Range.new(redis_config)
|
20
|
+
redis = Redis.new(redis_config)
|
21
|
+
last_update_time = redis.get(LAST_UPDATE_TIME_KEY)
|
22
|
+
|
23
|
+
sql = "SELECT neighbor, prefix, aspath, originas, nexthop, time from prefixes"
|
24
|
+
sql += " WHERE time >= '#{last_update_time}'" if last_update_time
|
25
|
+
sql += " ORDER BY time"
|
26
|
+
|
27
|
+
results = mysql.query(sql)
|
28
|
+
puts "#{results.count} prefixes found since '#{last_update_time}'"
|
29
|
+
results.each_with_index do |row, i|
|
30
|
+
range.add row["prefix"], {
|
31
|
+
as: row["originas"],
|
32
|
+
nexthop: row["nexthop"],
|
33
|
+
router: row["neighbor"],
|
34
|
+
aspath: row["aspath"],
|
35
|
+
timestamp: row["time"]
|
36
|
+
}
|
37
|
+
if ((i + 1) % 10000) == 0
|
38
|
+
puts "10000 rows added"
|
39
|
+
redis.set(LAST_UPDATE_TIME_KEY, row['time'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
mysql = Mysql2::Client.new mysql_config
|
46
|
+
update mysql, redis_config
|
47
|
+
rescue Exception => e
|
48
|
+
puts e.message
|
49
|
+
puts e.backtrace.inspect
|
50
|
+
else
|
51
|
+
puts "Done!"
|
52
|
+
ensure
|
53
|
+
mysql.close if mysql
|
54
|
+
end
|
data/iprange.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iprange/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "iprange"
|
8
|
+
spec.version = Iprange::VERSION
|
9
|
+
spec.authors = ["Juarez Bochi"]
|
10
|
+
spec.email = ["jbochi@gmail.com"]
|
11
|
+
spec.summary = "IP ranges on Redis"
|
12
|
+
spec.description = "Save IP ranges on Redis for fast lookup using interval sets."
|
13
|
+
spec.homepage = "https://github.com/globocom/iprange"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "redis"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/lib/iprange.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "iprange/version"
|
2
|
+
require "redis"
|
3
|
+
require "ipaddr"
|
4
|
+
|
5
|
+
module IPRange
|
6
|
+
class Range
|
7
|
+
def initialize(redis_config={}, redis_key="ip_table")
|
8
|
+
@redis = Redis.new redis_config
|
9
|
+
@redis_key = redis_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove(range)
|
13
|
+
@redis.irem(@redis_key, range)
|
14
|
+
@redis.del(metadata_key(range))
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(range, metadata={})
|
18
|
+
ipaddr_range = IPAddr.new(range).to_range
|
19
|
+
@redis.iadd(@redis_key, ipaddr_range.first.to_i, ipaddr_range.last.to_i, range)
|
20
|
+
hash = metadata_key(range)
|
21
|
+
@redis.mapped_hmset(hash, metadata) unless metadata.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(ip)
|
25
|
+
find_all(ip).first
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_all(ip)
|
29
|
+
ipaddr = IPAddr.new(ip)
|
30
|
+
ranges = @redis.istab(@redis_key, ipaddr.to_i)
|
31
|
+
ranges.map do |range|
|
32
|
+
metadata = @redis.hgetall(metadata_key(range))
|
33
|
+
{range: range}.merge(metadata)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def metadata_key(range)
|
38
|
+
"#{@redis_key}:#{range}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/nginx/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
This is a sample Nginx config file to query ips
|
2
|
+
|
3
|
+
$ ./start_nginx.sh
|
4
|
+
|
5
|
+
$ curl -s "localhost:8080/?ip=177.205.166.201" | python -m json.tool
|
6
|
+
{
|
7
|
+
"data": {
|
8
|
+
"as": "18881",
|
9
|
+
"aspath": "18881",
|
10
|
+
"nexthop": "200.219.138.113",
|
11
|
+
"range": "177.205.160.0/20",
|
12
|
+
"router": "201.7.183.65",
|
13
|
+
"timestamp": "2014-03-28 16:00:13 -0300"
|
14
|
+
},
|
15
|
+
"status": "ok"
|
16
|
+
}
|
17
|
+
|
data/nginx/nginx.conf
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
worker_processes 1;
|
2
|
+
pid /tmp/nginx.pid;
|
3
|
+
error_log "error.log" error;
|
4
|
+
|
5
|
+
events {
|
6
|
+
worker_connections 1024;
|
7
|
+
}
|
8
|
+
|
9
|
+
|
10
|
+
http {
|
11
|
+
default_type application/octet-stream;
|
12
|
+
sendfile on;
|
13
|
+
keepalive_timeout 65;
|
14
|
+
|
15
|
+
server {
|
16
|
+
listen 8080;
|
17
|
+
server_name localhost;
|
18
|
+
|
19
|
+
location / {
|
20
|
+
set $ip $arg_ip;
|
21
|
+
default_type 'text/plain';
|
22
|
+
content_by_lua_file 'range_lookup.lua';
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
local redis = require "redis"
|
2
|
+
local cjson = require "cjson"
|
3
|
+
|
4
|
+
local function ip_to_i(ip)
|
5
|
+
local o1,o2,o3,o4 = ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)")
|
6
|
+
return 2^24*o1 + 2^16*o2 + 2^8*o3 + o4
|
7
|
+
end
|
8
|
+
|
9
|
+
local function range_lookup(ip)
|
10
|
+
local red = redis:new()
|
11
|
+
red:set_timeout(1000)
|
12
|
+
local ok, err = red:connect("127.0.0.1", 6379)
|
13
|
+
if not ok then
|
14
|
+
return nil, "failed to connect: " .. err
|
15
|
+
end
|
16
|
+
|
17
|
+
if not ip or ip == "" then
|
18
|
+
return nil, "no ip provided"
|
19
|
+
end
|
20
|
+
|
21
|
+
local res, err = red:istab("ip_table", ip_to_i(ip))
|
22
|
+
if not res then
|
23
|
+
return nil, "failed to get range: " .. err
|
24
|
+
end
|
25
|
+
|
26
|
+
if not res[1] then
|
27
|
+
return nil, "range not found"
|
28
|
+
end
|
29
|
+
|
30
|
+
local range = res[1]
|
31
|
+
local metadata, err = red:hgetall("ip_table:" .. range)
|
32
|
+
|
33
|
+
if err then
|
34
|
+
return nil, "no metadata"
|
35
|
+
end
|
36
|
+
|
37
|
+
local response = {}
|
38
|
+
for i = 1, #metadata, 2 do
|
39
|
+
local key = metadata[i]
|
40
|
+
local value = metadata[i + 1]
|
41
|
+
response[key] = value
|
42
|
+
end
|
43
|
+
response.range = range
|
44
|
+
return response, nil
|
45
|
+
end
|
46
|
+
|
47
|
+
local response, err = range_lookup(ngx.var.ip)
|
48
|
+
if err then
|
49
|
+
ngx.say(cjson.encode({status="error", msg=err}))
|
50
|
+
else
|
51
|
+
ngx.say(cjson.encode({status="ok", data=response}))
|
52
|
+
end
|
data/nginx/redis.lua
ADDED
@@ -0,0 +1,466 @@
|
|
1
|
+
-- Copyright (C) Yichun Zhang (agentzh), CloudFlare Inc.
|
2
|
+
|
3
|
+
|
4
|
+
local sub = string.sub
|
5
|
+
local byte = string.byte
|
6
|
+
local tcp = ngx.socket.tcp
|
7
|
+
local concat = table.concat
|
8
|
+
local null = ngx.null
|
9
|
+
local pairs = pairs
|
10
|
+
local unpack = unpack
|
11
|
+
local setmetatable = setmetatable
|
12
|
+
local tonumber = tonumber
|
13
|
+
local error = error
|
14
|
+
|
15
|
+
|
16
|
+
local ok, new_tab = pcall(require, "table.new")
|
17
|
+
if not ok then
|
18
|
+
new_tab = function (narr, nrec) return {} end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
local _M = new_tab(0, 155)
|
23
|
+
_M._VERSION = '0.20'
|
24
|
+
|
25
|
+
|
26
|
+
local commands = {
|
27
|
+
"append", "auth", "bgrewriteaof",
|
28
|
+
"bgsave", "bitcount", "bitop",
|
29
|
+
"blpop", "brpop",
|
30
|
+
"brpoplpush", "client", "config",
|
31
|
+
"dbsize",
|
32
|
+
"debug", "decr", "decrby",
|
33
|
+
"del", "discard", "dump",
|
34
|
+
"echo",
|
35
|
+
"eval", "exec", "exists",
|
36
|
+
"expire", "expireat", "flushall",
|
37
|
+
"flushdb", "get", "getbit",
|
38
|
+
"getrange", "getset", "hdel",
|
39
|
+
"hexists", "hget", "hgetall",
|
40
|
+
"hincrby", "hincrbyfloat", "hkeys",
|
41
|
+
"hlen",
|
42
|
+
"hmget", --[[ "hmset", ]] "hscan",
|
43
|
+
"hset",
|
44
|
+
"hsetnx", "hvals", "incr",
|
45
|
+
"incrby", "incrbyfloat", "info",
|
46
|
+
"keys",
|
47
|
+
"lastsave", "lindex", "linsert",
|
48
|
+
"llen", "lpop", "lpush",
|
49
|
+
"lpushx", "lrange", "lrem",
|
50
|
+
"lset", "ltrim", "mget",
|
51
|
+
"migrate",
|
52
|
+
"monitor", "move", "mset",
|
53
|
+
"msetnx", "multi", "object",
|
54
|
+
"persist", "pexpire", "pexpireat",
|
55
|
+
"ping", "psetex", --[[ "psubscribe", ]]
|
56
|
+
"pttl",
|
57
|
+
"publish", --[[ "punsubscribe", ]] "pubsub",
|
58
|
+
"quit",
|
59
|
+
"randomkey", "rename", "renamenx",
|
60
|
+
"restore",
|
61
|
+
"rpop", "rpoplpush", "rpush",
|
62
|
+
"rpushx", "sadd", "save",
|
63
|
+
"scan", "scard", "script",
|
64
|
+
"sdiff", "sdiffstore",
|
65
|
+
"select", "set", "setbit",
|
66
|
+
"setex", "setnx", "setrange",
|
67
|
+
"shutdown", "sinter", "sinterstore",
|
68
|
+
"sismember", "slaveof", "slowlog",
|
69
|
+
"smembers", "smove", "sort",
|
70
|
+
"spop", "srandmember", "srem",
|
71
|
+
"sscan",
|
72
|
+
"strlen", --[[ "subscribe", ]] "sunion",
|
73
|
+
"sunionstore", "sync", "time",
|
74
|
+
"ttl",
|
75
|
+
"type", --[[ "unsubscribe", ]] "unwatch",
|
76
|
+
"watch", "zadd", "zcard",
|
77
|
+
"zcount", "zincrby", "zinterstore",
|
78
|
+
"zrange", "zrangebyscore", "zrank",
|
79
|
+
"zrem", "zremrangebyrank", "zremrangebyscore",
|
80
|
+
"zrevrange", "zrevrangebyscore", "zrevrank",
|
81
|
+
"zscan",
|
82
|
+
"zscore", "zunionstore", "evalsha",
|
83
|
+
"istab"
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
local sub_commands = {
|
88
|
+
"subscribe", "psubscribe"
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
local unsub_commands = {
|
93
|
+
"unsubscribe", "punsubscribe"
|
94
|
+
}
|
95
|
+
|
96
|
+
|
97
|
+
local mt = { __index = _M }
|
98
|
+
|
99
|
+
|
100
|
+
function _M.new(self)
|
101
|
+
local sock, err = tcp()
|
102
|
+
if not sock then
|
103
|
+
return nil, err
|
104
|
+
end
|
105
|
+
return setmetatable({ sock = sock }, mt)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
function _M.set_timeout(self, timeout)
|
110
|
+
local sock = self.sock
|
111
|
+
if not sock then
|
112
|
+
return nil, "not initialized"
|
113
|
+
end
|
114
|
+
|
115
|
+
return sock:settimeout(timeout)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
function _M.connect(self, ...)
|
120
|
+
local sock = self.sock
|
121
|
+
if not sock then
|
122
|
+
return nil, "not initialized"
|
123
|
+
end
|
124
|
+
|
125
|
+
self.subscribed = nil
|
126
|
+
|
127
|
+
return sock:connect(...)
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
function _M.set_keepalive(self, ...)
|
132
|
+
local sock = self.sock
|
133
|
+
if not sock then
|
134
|
+
return nil, "not initialized"
|
135
|
+
end
|
136
|
+
|
137
|
+
if self.subscribed then
|
138
|
+
return nil, "subscribed state"
|
139
|
+
end
|
140
|
+
|
141
|
+
return sock:setkeepalive(...)
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
function _M.get_reused_times(self)
|
146
|
+
local sock = self.sock
|
147
|
+
if not sock then
|
148
|
+
return nil, "not initialized"
|
149
|
+
end
|
150
|
+
|
151
|
+
return sock:getreusedtimes()
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
local function close(self)
|
156
|
+
local sock = self.sock
|
157
|
+
if not sock then
|
158
|
+
return nil, "not initialized"
|
159
|
+
end
|
160
|
+
|
161
|
+
return sock:close()
|
162
|
+
end
|
163
|
+
_M.close = close
|
164
|
+
|
165
|
+
|
166
|
+
local function _read_reply(self, sock)
|
167
|
+
local line, err = sock:receive()
|
168
|
+
if not line then
|
169
|
+
if err == "timeout" and not self.subscribed then
|
170
|
+
sock:close()
|
171
|
+
end
|
172
|
+
return nil, err
|
173
|
+
end
|
174
|
+
|
175
|
+
local prefix = byte(line)
|
176
|
+
|
177
|
+
if prefix == 36 then -- char '$'
|
178
|
+
-- print("bulk reply")
|
179
|
+
|
180
|
+
local size = tonumber(sub(line, 2))
|
181
|
+
if size < 0 then
|
182
|
+
return null
|
183
|
+
end
|
184
|
+
|
185
|
+
local data, err = sock:receive(size)
|
186
|
+
if not data then
|
187
|
+
if err == "timeout" then
|
188
|
+
sock:close()
|
189
|
+
end
|
190
|
+
return nil, err
|
191
|
+
end
|
192
|
+
|
193
|
+
local dummy, err = sock:receive(2) -- ignore CRLF
|
194
|
+
if not dummy then
|
195
|
+
return nil, err
|
196
|
+
end
|
197
|
+
|
198
|
+
return data
|
199
|
+
|
200
|
+
elseif prefix == 43 then -- char '+'
|
201
|
+
-- print("status reply")
|
202
|
+
|
203
|
+
return sub(line, 2)
|
204
|
+
|
205
|
+
elseif prefix == 42 then -- char '*'
|
206
|
+
local n = tonumber(sub(line, 2))
|
207
|
+
|
208
|
+
-- print("multi-bulk reply: ", n)
|
209
|
+
if n < 0 then
|
210
|
+
return null
|
211
|
+
end
|
212
|
+
|
213
|
+
local vals = new_tab(n, 0);
|
214
|
+
local nvals = 0
|
215
|
+
for i = 1, n do
|
216
|
+
local res, err = _read_reply(self, sock)
|
217
|
+
if res then
|
218
|
+
nvals = nvals + 1
|
219
|
+
vals[nvals] = res
|
220
|
+
|
221
|
+
elseif res == nil then
|
222
|
+
return nil, err
|
223
|
+
|
224
|
+
else
|
225
|
+
-- be a valid redis error value
|
226
|
+
nvals = nvals + 1
|
227
|
+
vals[nvals] = {false, err}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
return vals
|
232
|
+
|
233
|
+
elseif prefix == 58 then -- char ':'
|
234
|
+
-- print("integer reply")
|
235
|
+
return tonumber(sub(line, 2))
|
236
|
+
|
237
|
+
elseif prefix == 45 then -- char '-'
|
238
|
+
-- print("error reply: ", n)
|
239
|
+
|
240
|
+
return false, sub(line, 2)
|
241
|
+
|
242
|
+
else
|
243
|
+
return nil, "unkown prefix: \"" .. prefix .. "\""
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
local function _gen_req(args)
|
249
|
+
local nargs = #args
|
250
|
+
|
251
|
+
local req = new_tab(nargs + 1, 0)
|
252
|
+
req[1] = "*" .. nargs .. "\r\n"
|
253
|
+
local nbits = 1
|
254
|
+
|
255
|
+
for i = 1, nargs do
|
256
|
+
local arg = args[i]
|
257
|
+
nbits = nbits + 1
|
258
|
+
|
259
|
+
if not arg then
|
260
|
+
req[nbits] = "$-1\r\n"
|
261
|
+
|
262
|
+
else
|
263
|
+
if type(arg) ~= "string" then
|
264
|
+
arg = tostring(arg)
|
265
|
+
end
|
266
|
+
req[nbits] = "$" .. #arg .. "\r\n" .. arg .. "\r\n"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
-- it is faster to do string concatenation on the Lua land
|
271
|
+
return concat(req)
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
local function _do_cmd(self, ...)
|
276
|
+
local args = {...}
|
277
|
+
|
278
|
+
local sock = self.sock
|
279
|
+
if not sock then
|
280
|
+
return nil, "not initialized"
|
281
|
+
end
|
282
|
+
|
283
|
+
local req = _gen_req(args)
|
284
|
+
|
285
|
+
local reqs = self._reqs
|
286
|
+
if reqs then
|
287
|
+
reqs[#reqs + 1] = req
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
-- print("request: ", table.concat(req))
|
292
|
+
|
293
|
+
local bytes, err = sock:send(req)
|
294
|
+
if not bytes then
|
295
|
+
return nil, err
|
296
|
+
end
|
297
|
+
|
298
|
+
return _read_reply(self, sock)
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
local function _check_subscribed(self, res)
|
303
|
+
if type(res) == "table"
|
304
|
+
and (res[1] == "unsubscribe" or res[1] == "punsubscribe")
|
305
|
+
and res[3] == 0
|
306
|
+
then
|
307
|
+
self.subscribed = nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
function _M.read_reply(self)
|
313
|
+
local sock = self.sock
|
314
|
+
if not sock then
|
315
|
+
return nil, "not initialized"
|
316
|
+
end
|
317
|
+
|
318
|
+
if not self.subscribed then
|
319
|
+
return nil, "not subscribed"
|
320
|
+
end
|
321
|
+
|
322
|
+
local res, err = _read_reply(self, sock)
|
323
|
+
_check_subscribed(self, res)
|
324
|
+
|
325
|
+
return res, err
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
for i = 1, #commands do
|
330
|
+
local cmd = commands[i]
|
331
|
+
|
332
|
+
_M[cmd] =
|
333
|
+
function (self, ...)
|
334
|
+
return _do_cmd(self, cmd, ...)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
for i = 1, #sub_commands do
|
340
|
+
local cmd = sub_commands[i]
|
341
|
+
|
342
|
+
_M[cmd] =
|
343
|
+
function (self, ...)
|
344
|
+
self.subscribed = true
|
345
|
+
return _do_cmd(self, cmd, ...)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
for i = 1, #unsub_commands do
|
351
|
+
local cmd = unsub_commands[i]
|
352
|
+
|
353
|
+
_M[cmd] =
|
354
|
+
function (self, ...)
|
355
|
+
local res, err = _do_cmd(self, cmd, ...)
|
356
|
+
_check_subscribed(self, res)
|
357
|
+
return res, err
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
function _M.hmset(self, hashname, ...)
|
363
|
+
local args = {...}
|
364
|
+
if #args == 1 then
|
365
|
+
local t = args[1]
|
366
|
+
|
367
|
+
local n = 0
|
368
|
+
for k, v in pairs(t) do
|
369
|
+
n = n + 2
|
370
|
+
end
|
371
|
+
|
372
|
+
local array = new_tab(n, 0)
|
373
|
+
|
374
|
+
local i = 0
|
375
|
+
for k, v in pairs(t) do
|
376
|
+
array[i + 1] = k
|
377
|
+
array[i + 2] = v
|
378
|
+
i = i + 2
|
379
|
+
end
|
380
|
+
-- print("key", hashname)
|
381
|
+
return _do_cmd(self, "hmset", hashname, unpack(array))
|
382
|
+
end
|
383
|
+
|
384
|
+
-- backwards compatibility
|
385
|
+
return _do_cmd(self, "hmset", hashname, ...)
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
function _M.init_pipeline(self, n)
|
390
|
+
self._reqs = new_tab(n or 4, 0)
|
391
|
+
end
|
392
|
+
|
393
|
+
|
394
|
+
function _M.cancel_pipeline(self)
|
395
|
+
self._reqs = nil
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
function _M.commit_pipeline(self)
|
400
|
+
local reqs = self._reqs
|
401
|
+
if not reqs then
|
402
|
+
return nil, "no pipeline"
|
403
|
+
end
|
404
|
+
|
405
|
+
self._reqs = nil
|
406
|
+
|
407
|
+
local sock = self.sock
|
408
|
+
if not sock then
|
409
|
+
return nil, "not initialized"
|
410
|
+
end
|
411
|
+
|
412
|
+
local bytes, err = sock:send(reqs)
|
413
|
+
if not bytes then
|
414
|
+
return nil, err
|
415
|
+
end
|
416
|
+
|
417
|
+
local nvals = 0
|
418
|
+
local nreqs = #reqs
|
419
|
+
local vals = new_tab(nreqs, 0)
|
420
|
+
for i = 1, nreqs do
|
421
|
+
local res, err = _read_reply(self, sock)
|
422
|
+
if res then
|
423
|
+
nvals = nvals + 1
|
424
|
+
vals[nvals] = res
|
425
|
+
|
426
|
+
elseif res == nil then
|
427
|
+
if err == "timeout" then
|
428
|
+
close(self)
|
429
|
+
end
|
430
|
+
return nil, err
|
431
|
+
|
432
|
+
else
|
433
|
+
-- be a valid redis error value
|
434
|
+
nvals = nvals + 1
|
435
|
+
vals[nvals] = {false, err}
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
return vals
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
function _M.array_to_hash(self, t)
|
444
|
+
local n = #t
|
445
|
+
-- print("n = ", n)
|
446
|
+
local h = new_tab(0, n / 2)
|
447
|
+
for i = 1, n, 2 do
|
448
|
+
h[t[i]] = t[i + 1]
|
449
|
+
end
|
450
|
+
return h
|
451
|
+
end
|
452
|
+
|
453
|
+
|
454
|
+
function _M.add_commands(...)
|
455
|
+
local cmds = {...}
|
456
|
+
for i = 1, #cmds do
|
457
|
+
local cmd = cmds[i]
|
458
|
+
_M[cmd] =
|
459
|
+
function (self, ...)
|
460
|
+
return _do_cmd(self, cmd, ...)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
return _M
|
@@ -0,0 +1 @@
|
|
1
|
+
/usr/local/openresty/nginx/sbin/nginx -c `pwd`/nginx.conf -p `pwd` -s reload
|
@@ -0,0 +1 @@
|
|
1
|
+
/usr/local/openresty/nginx/sbin/nginx -c `pwd`/nginx.conf -p `pwd`
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe IPRange::Range do
|
4
|
+
before do
|
5
|
+
range = IPRange::Range.new()
|
6
|
+
range.remove("0.0.0.0/0")
|
7
|
+
range.remove("192.168.0.1/24")
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".initialize" do
|
11
|
+
subject do
|
12
|
+
IPRange::Range
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should accept redis config as argument" do
|
16
|
+
redis_config = {host: "127.0.0.1"}
|
17
|
+
expect(subject.new(redis_config))
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should use config to connect to redis" do
|
21
|
+
redis_config = double()
|
22
|
+
Redis.should_receive(:new).with(redis_config)
|
23
|
+
subject.new(redis_config)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when a range is added" do
|
28
|
+
subject do
|
29
|
+
IPRange::Range.new
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
subject.add("192.168.0.1/24")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should find it back" do
|
37
|
+
response = subject.find("192.168.0.20")
|
38
|
+
expect(response).to eq({range: "192.168.0.1/24"})
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return nil for smaller ip that is not in range" do
|
42
|
+
response = subject.find("192.167.255.255")
|
43
|
+
expect(response).to be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return nil for greater ip that is not in range" do
|
47
|
+
response = subject.find("192.169.0.1")
|
48
|
+
expect(response).to be_nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "when a range is added with metadata" do
|
53
|
+
subject do
|
54
|
+
IPRange::Range.new
|
55
|
+
end
|
56
|
+
|
57
|
+
before do
|
58
|
+
subject.add("192.168.0.1/24", some: "data", more: "metadata")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should find it back" do
|
62
|
+
response = subject.find("192.168.0.20")
|
63
|
+
expect(response[:range]).to eq("192.168.0.1/24")
|
64
|
+
expect(response["some"]).to eq("data")
|
65
|
+
expect(response["more"]).to eq("metadata")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when there is multiple ranges with overlap" do
|
70
|
+
subject do
|
71
|
+
IPRange::Range.new
|
72
|
+
end
|
73
|
+
|
74
|
+
before do
|
75
|
+
subject.add("0.0.0.0/0")
|
76
|
+
subject.add("192.168.0.1/24")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should find the most specific range" do
|
80
|
+
response = subject.find("192.168.0.20")
|
81
|
+
expect(response[:range]).to eq("192.168.0.1/24")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should find all the ranges" do
|
85
|
+
response = subject.find_all("192.168.0.20")
|
86
|
+
expect(response).to eq([{:range=>"192.168.0.1/24"}, {:range=>"0.0.0.0/0"}])
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "iprange"
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iprange
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Juarez Bochi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Save IP ranges on Redis for fast lookup using interval sets.
|
70
|
+
email:
|
71
|
+
- jbochi@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- examples/gixlg_importer.rb
|
83
|
+
- iprange.gemspec
|
84
|
+
- lib/iprange.rb
|
85
|
+
- lib/iprange/version.rb
|
86
|
+
- nginx/README.md
|
87
|
+
- nginx/logs/.gitkeep
|
88
|
+
- nginx/nginx.conf
|
89
|
+
- nginx/range_lookup.lua
|
90
|
+
- nginx/redis.lua
|
91
|
+
- nginx/reload_nginx.sh
|
92
|
+
- nginx/start_nginx.sh
|
93
|
+
- spec/iprange_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: https://github.com/globocom/iprange
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.0.14
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: IP ranges on Redis
|
119
|
+
test_files:
|
120
|
+
- spec/iprange_spec.rb
|
121
|
+
- spec/spec_helper.rb
|