iprange 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ nginx/logs/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format doc
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in iprange.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Iprange
2
+ VERSION = "0.0.2"
3
+ end
@@ -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
+
@@ -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
@@ -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
@@ -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