├── .gitignore ├── .rspec ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── examples └── gixlg_importer.rb ├── iprange.gemspec ├── lib ├── iprange.rb └── iprange │ └── version.rb ├── nginx ├── README.md ├── logs │ └── .gitkeep ├── nginx.conf ├── range_lookup.lua ├── redis.lua ├── reload_nginx.sh └── start_nginx.sh └── spec ├── iprange_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 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/ 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format doc 2 | --color -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in iprange.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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/globocom/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 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /examples/gixlg_importer.rb: -------------------------------------------------------------------------------- 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 55 | -------------------------------------------------------------------------------- /iprange.gemspec: -------------------------------------------------------------------------------- 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 27 | -------------------------------------------------------------------------------- /lib/iprange.rb: -------------------------------------------------------------------------------- 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.pipelined do 14 | @redis.irem(@redis_key, range) 15 | @redis.del(metadata_key(range)) 16 | end 17 | end 18 | 19 | def add(range, metadata={}) 20 | ipaddr_range = IPAddr.new(range).to_range 21 | range = "#{metadata[:key]}:#{range}" if metadata[:key] 22 | hash = metadata_key(range) 23 | 24 | @redis.pipelined do 25 | @redis.iadd(@redis_key, ipaddr_range.first.to_i, ipaddr_range.last.to_i, range) 26 | @redis.mapped_hmset(hash, metadata) unless metadata.empty? 27 | end 28 | end 29 | 30 | def find(ip) 31 | find_all(ip).first 32 | end 33 | 34 | def find_all(ip) 35 | ipaddr = IPAddr.new(ip) 36 | ranges = @redis.istab(@redis_key, ipaddr.to_i) 37 | ranges.map do |range| 38 | metadata = @redis.hgetall(metadata_key(range)) 39 | {range: range}.merge(metadata) 40 | end 41 | end 42 | 43 | def metadata_key(range) 44 | "#{@redis_key}:#{range}" 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/iprange/version.rb: -------------------------------------------------------------------------------- 1 | module Iprange 2 | VERSION = "0.0.4" 3 | end 4 | -------------------------------------------------------------------------------- /nginx/README.md: -------------------------------------------------------------------------------- 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 | 18 | -------------------------------------------------------------------------------- /nginx/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/iprange/77e49b02a65007409a799ed6a72f4440f1f7a798/nginx/logs/.gitkeep -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 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 | } 26 | -------------------------------------------------------------------------------- /nginx/range_lookup.lua: -------------------------------------------------------------------------------- 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 53 | -------------------------------------------------------------------------------- /nginx/redis.lua: -------------------------------------------------------------------------------- 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 467 | -------------------------------------------------------------------------------- /nginx/reload_nginx.sh: -------------------------------------------------------------------------------- 1 | /usr/local/openresty/nginx/sbin/nginx -c `pwd`/nginx.conf -p `pwd` -s reload 2 | -------------------------------------------------------------------------------- /nginx/start_nginx.sh: -------------------------------------------------------------------------------- 1 | /usr/local/openresty/nginx/sbin/nginx -c `pwd`/nginx.conf -p `pwd` 2 | -------------------------------------------------------------------------------- /spec/iprange_spec.rb: -------------------------------------------------------------------------------- 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 a range is added with extra key" do 70 | after :each do 71 | range = IPRange::Range.new() 72 | range.remove("router1:192.168.0.1/24") 73 | end 74 | 75 | it "should find it with the key" do 76 | range = IPRange::Range.new 77 | range.add("192.168.0.1/24", key: "router1") 78 | 79 | response = range.find("192.168.0.20") 80 | expect(response[:range]).to eq("router1:192.168.0.1/24") 81 | end 82 | end 83 | 84 | describe "when there is multiple ranges with overlap" do 85 | subject do 86 | IPRange::Range.new 87 | end 88 | 89 | before do 90 | subject.add("0.0.0.0/0") 91 | subject.add("192.168.0.1/24") 92 | end 93 | 94 | it "should find the most specific range" do 95 | response = subject.find("192.168.0.20") 96 | expect(response[:range]).to eq("192.168.0.1/24") 97 | end 98 | 99 | it "should find all the ranges" do 100 | response = subject.find_all("192.168.0.20") 101 | expect(response).to eq([{:range=>"192.168.0.1/24"}, {:range=>"0.0.0.0/0"}]) 102 | end 103 | 104 | end 105 | 106 | end 107 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "iprange" 2 | --------------------------------------------------------------------------------