├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── dist.ini ├── lib └── resty │ └── iputils.lua ├── lua-resty-iputils-0.3.0-2.rockspec ├── t ├── 01-ip2bin.t ├── 02-cidr.t └── 03-misc.t └── util └── lua-releng.pl /.gitignore: -------------------------------------------------------------------------------- 1 | t/servroot/ 2 | t/error.log 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Hamish Forbes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | TEST_FILE ?= t 8 | 9 | .PHONY: all test leak 10 | 11 | all: ; 12 | 13 | install: all 14 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty 15 | $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/ 16 | 17 | leak: all 18 | TEST_NGINX_CHECK_LEAK=1 TEST_NGINX_NO_SHUFFLE=1 PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r $(TEST_FILE) 19 | 20 | test: all 21 | TEST_NGINX_NO_SHUFFLE=1 PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r $(TEST_FILE) 22 | util/lua-releng.pl 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-iputils 2 | 3 | Collection of utility functions for working with IP addresses. 4 | 5 | # Overview 6 | 7 | ``` 8 | init_by_lua_block { 9 | local iputils = require("resty.iputils") 10 | iputils.enable_lrucache() 11 | local whitelist_ips = { 12 | "127.0.0.1", 13 | "10.10.10.0/24", 14 | "192.168.0.0/16", 15 | } 16 | 17 | -- WARNING: Global variable, recommend this is cached at the module level 18 | -- https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker 19 | whitelist = iputils.parse_cidrs(whitelist_ips) 20 | } 21 | 22 | access_by_lua_block { 23 | local iputils = require("resty.iputils") 24 | if not iputils.ip_in_cidrs(ngx.var.remote_addr, whitelist) then 25 | return ngx.exit(ngx.HTTP_FORBIDDEN) 26 | end 27 | } 28 | ``` 29 | 30 | # Methods 31 | ### enable_lrucache 32 | `syntax: ok, err = iputils.enable_lrucache(size?)` 33 | 34 | Creates a global lrucache object for caching ip2bin lookups. 35 | 36 | Size is optional and defaults to 4000 entries (~1MB per worker) 37 | 38 | Calling this repeatedly will reset the cache 39 | 40 | ### ip2bin 41 | `syntax: bin_ip, bin_octets = iputils.ip2bin(ip)` 42 | 43 | Returns the binary representation of an IPv4 address and a table containing the binary representation of each octet 44 | 45 | Returns `nil` and and error message for bad IPs 46 | 47 | ### parse_cidr 48 | `syntax: lower, upper = iputils.parse_cidr(cidr)` 49 | 50 | Returns a binary representation of the lowest (network) and highest (broadcast) addresses of an IPv4 network. 51 | 52 | ### parse_cidrs 53 | `syntax: parsed = iputils.parse_cidrs(cidrs)` 54 | 55 | Takes a table of CIDR format IPV4 networks and returns a table of tables containg the lower and upper addresses. 56 | 57 | If an invalid network is in the table an error is logged and the other networks are returned 58 | 59 | ### ip_in_cidrs 60 | `syntax: bool, err = iputils.ip_in_cidrs(ip, cidrs)` 61 | 62 | Takes a string IPv4 address and a table of parsed CIDRs (e.g. from `iputils.parse_cidrs`). 63 | 64 | Returns a `true` or `false` if the IP exists within *any* of the specified networks. 65 | 66 | Returns `nil` and an error message with an invalid IP 67 | 68 | ### binip_in_cidrs 69 | `syntax: bool, err = iputils.binip_in_cidrs(bin_ip, cidrs)` 70 | 71 | Takes a nginx binary IPv4 address (e.g. `ngx.var.binary_remote_addr`) and a table of parsed CIDRs (e.g. from `iputils.parse_cidrs`). 72 | 73 | This method is much faster than `ip_in_cidrs()` if the IP being checked is already available as a binary representation. 74 | 75 | Returns a `true` or `false` if the IP exists within *any* of the specified networks. 76 | 77 | Returns `nil` and an error message with an invalid IP 78 | 79 | ## TODO 80 | * IPv6 support - Alternative library supporting ipv6 - [lua-libcidr-ffi](https://github.com/GUI/lua-libcidr-ffi) 81 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=lua-resty-iputils 2 | abstract=Collection of utility functions for working with IP addresses. 3 | author=Hamish Forbes 4 | is_original=yes 5 | license=mit 6 | lib_dir=lib 7 | repo_link=https://github.com/hamishforbes/lua-resty-iputils 8 | main_module=lib/resty/iputils.lua 9 | requires = luajit 10 | -------------------------------------------------------------------------------- /lib/resty/iputils.lua: -------------------------------------------------------------------------------- 1 | local ipairs, tonumber, tostring, type = ipairs, tonumber, tostring, type 2 | local bit = require("bit") 3 | local lshift = bit.lshift 4 | local band = bit.band 5 | local bor = bit.bor 6 | local xor = bit.bxor 7 | local byte = string.byte 8 | local str_find = string.find 9 | local str_sub = string.sub 10 | 11 | local lrucache = nil 12 | 13 | local _M = { 14 | _VERSION = '0.3.0', 15 | } 16 | 17 | local mt = { __index = _M } 18 | 19 | 20 | -- Precompute binary subnet masks... 21 | local bin_masks = {} 22 | for i=0,32 do 23 | bin_masks[tostring(i)] = lshift((2^i)-1, 32-i) 24 | end 25 | -- ... and their inverted counterparts 26 | local bin_inverted_masks = {} 27 | for i=0,32 do 28 | local i = tostring(i) 29 | bin_inverted_masks[i] = xor(bin_masks[i], bin_masks["32"]) 30 | end 31 | 32 | local log_err 33 | if ngx then 34 | log_err = function(...) 35 | ngx.log(ngx.ERR, ...) 36 | end 37 | else 38 | log_err = function(...) 39 | print(...) 40 | end 41 | end 42 | 43 | 44 | local function enable_lrucache(size) 45 | local size = size or 4000 -- Cache the last 4000 IPs (~1MB memory) by default 46 | local lrucache_obj, err = require("resty.lrucache").new(size) 47 | if not lrucache_obj then 48 | return nil, "failed to create the cache: " .. (err or "unknown") 49 | end 50 | lrucache = lrucache_obj 51 | return true 52 | end 53 | _M.enable_lrucache = enable_lrucache 54 | 55 | 56 | local function split_octets(input) 57 | local pos = 0 58 | local prev = 0 59 | local octs = {} 60 | 61 | for i=1, 4 do 62 | pos = str_find(input, ".", prev, true) 63 | if pos then 64 | if i == 4 then 65 | -- Should not have a match after 4 octets 66 | return nil, "Invalid IP" 67 | end 68 | octs[i] = str_sub(input, prev, pos-1) 69 | elseif i == 4 then 70 | -- Last octet, get everything to the end 71 | octs[i] = str_sub(input, prev, -1) 72 | break 73 | else 74 | return nil, "Invalid IP" 75 | end 76 | prev = pos +1 77 | end 78 | 79 | return octs 80 | end 81 | 82 | 83 | local function unsign(bin) 84 | if bin < 0 then 85 | return 4294967296 + bin 86 | end 87 | return bin 88 | end 89 | 90 | 91 | local function ip2bin(ip) 92 | if lrucache then 93 | local get = lrucache:get(ip) 94 | if get then 95 | return get[1], get[2] 96 | end 97 | end 98 | 99 | if type(ip) ~= "string" then 100 | return nil, "IP must be a string" 101 | end 102 | 103 | local octets = split_octets(ip) 104 | if not octets or #octets ~= 4 then 105 | return nil, "Invalid IP" 106 | end 107 | 108 | -- Return the binary representation of an IP and a table of binary octets 109 | local bin_octets = {} 110 | local bin_ip = 0 111 | 112 | for i,octet in ipairs(octets) do 113 | local bin_octet = tonumber(octet) 114 | if not bin_octet or bin_octet < 0 or bin_octet > 255 then 115 | return nil, "Invalid octet: "..tostring(octet) 116 | end 117 | bin_octets[i] = bin_octet 118 | bin_ip = bor(lshift(bin_octet, 8*(4-i) ), bin_ip) 119 | end 120 | 121 | bin_ip = unsign(bin_ip) 122 | if lrucache then 123 | lrucache:set(ip, {bin_ip, bin_octets}) 124 | end 125 | return bin_ip, bin_octets 126 | end 127 | _M.ip2bin = ip2bin 128 | 129 | 130 | local function split_cidr(input) 131 | local pos = str_find(input, "/", 0, true) 132 | if not pos then 133 | return {input} 134 | end 135 | return {str_sub(input, 1, pos-1), str_sub(input, pos+1, -1)} 136 | end 137 | 138 | 139 | local function parse_cidr(cidr) 140 | local mask_split = split_cidr(cidr, '/') 141 | local net = mask_split[1] 142 | local mask = mask_split[2] or "32" 143 | local mask_num = tonumber(mask) 144 | 145 | if not mask_num or (mask_num > 32 or mask_num < 0) then 146 | return nil, "Invalid prefix: /"..tostring(mask) 147 | end 148 | 149 | local bin_net, err = ip2bin(net) -- Convert IP to binary 150 | if not bin_net then 151 | return nil, err 152 | end 153 | local bin_mask = bin_masks[mask] -- Get masks 154 | local bin_inv_mask = bin_inverted_masks[mask] 155 | 156 | local lower = band(bin_net, bin_mask) -- Network address 157 | local upper = bor(lower, bin_inv_mask) -- Broadcast address 158 | return unsign(lower), unsign(upper) 159 | end 160 | _M.parse_cidr = parse_cidr 161 | 162 | 163 | local function parse_cidrs(cidrs) 164 | local out = {} 165 | local i = 1 166 | for _,cidr in ipairs(cidrs) do 167 | local lower, upper = parse_cidr(cidr) 168 | if not lower then 169 | log_err("Error parsing '", cidr, "': ", upper) 170 | else 171 | out[i] = {lower, upper} 172 | i = i+1 173 | end 174 | end 175 | return out 176 | end 177 | _M.parse_cidrs = parse_cidrs 178 | 179 | 180 | local function ip_in_cidrs(ip, cidrs) 181 | local bin_ip, bin_octets = ip2bin(ip) 182 | if not bin_ip then 183 | return nil, bin_octets 184 | end 185 | 186 | for _,cidr in ipairs(cidrs) do 187 | if bin_ip >= cidr[1] and bin_ip <= cidr[2] then 188 | return true 189 | end 190 | end 191 | return false 192 | end 193 | _M.ip_in_cidrs = ip_in_cidrs 194 | 195 | 196 | local function binip_in_cidrs(bin_ip_ngx, cidrs) 197 | if 4 ~= #bin_ip_ngx then 198 | return false, "invalid IP address" 199 | end 200 | 201 | local bin_ip = 0 202 | for i=1,4 do 203 | bin_ip = bor(lshift(bin_ip, 8), byte(bin_ip_ngx, i)) 204 | end 205 | bin_ip = unsign(bin_ip) 206 | 207 | for _,cidr in ipairs(cidrs) do 208 | if bin_ip >= cidr[1] and bin_ip <= cidr[2] then 209 | return true 210 | end 211 | end 212 | return false 213 | end 214 | _M.binip_in_cidrs = binip_in_cidrs 215 | 216 | return _M 217 | -------------------------------------------------------------------------------- /lua-resty-iputils-0.3.0-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-iputils" 2 | version = "0.3.0-2" 3 | 4 | source = { 5 | url = "git+https://github.com/hamishforbes/lua-resty-iputils.git", 6 | tag = "v0.3.0", 7 | } 8 | 9 | description = { 10 | summary = "Utility functions for working with IP addresses in Openresty", 11 | license = "MIT", 12 | } 13 | 14 | dependencies = { 15 | "lua >= 5.1", 16 | } 17 | 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | ["resty.iputils"] = "lib/resty/iputils.lua", 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /t/01-ip2bin.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | use Cwd qw(cwd); 3 | 4 | plan tests => repeat_each() * 27; 5 | 6 | my $pwd = cwd(); 7 | 8 | $ENV{TEST_LEDGE_REDIS_DATABASE} ||= 1; 9 | 10 | our $HttpConfig = qq{ 11 | lua_package_path "$pwd/lib/?.lua;;"; 12 | }; 13 | 14 | no_long_string(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | === TEST 1: ip2bin returns binary representation of IP 19 | --- http_config eval 20 | "$::HttpConfig" 21 | . q{ 22 | } 23 | --- config 24 | location /a { 25 | content_by_lua ' 26 | local iputils = require("resty.iputils") 27 | local bin_ip, bin_octets = iputils.ip2bin("127.0.0.1") 28 | ngx.say(bin_ip) 29 | '; 30 | } 31 | --- request 32 | GET /a 33 | --- no_error_log 34 | [error] 35 | --- response_body 36 | 2130706433 37 | 38 | === TEST 1b: ip2bin returns an unsigned binary representation of IP 39 | --- http_config eval 40 | "$::HttpConfig" 41 | . q{ 42 | } 43 | --- config 44 | location /a { 45 | content_by_lua_block { 46 | local iputils = require("resty.iputils") 47 | local bin_ip, bin_octets = iputils.ip2bin("255.255.255.255") 48 | ngx.say(bin_ip) 49 | local bin_ip, bin_octets = iputils.ip2bin("250.250.250.250") 50 | ngx.say(bin_ip) 51 | } 52 | } 53 | --- request 54 | GET /a 55 | --- no_error_log 56 | [error] 57 | --- response_body 58 | 4294967295 59 | 4210752250 60 | 61 | 62 | === TEST 2: ip2bin returns binary representation of each octet 63 | --- http_config eval 64 | "$::HttpConfig" 65 | . q{ 66 | } 67 | --- config 68 | location /a { 69 | content_by_lua ' 70 | local iputils = require("resty.iputils") 71 | local bin_ip, bin_octets = iputils.ip2bin("127.0.0.1") 72 | for _, octet in ipairs(bin_octets) do 73 | ngx.say(octet) 74 | end 75 | '; 76 | } 77 | --- request 78 | GET /a 79 | --- no_error_log 80 | [error] 81 | --- response_body 82 | 127 83 | 0 84 | 0 85 | 1 86 | 87 | === TEST 3: ip2bin returns error on bad ip form - too few octets 88 | --- http_config eval 89 | "$::HttpConfig" 90 | . q{ 91 | } 92 | --- config 93 | location /a { 94 | content_by_lua ' 95 | local iputils = require("resty.iputils") 96 | local bin_ip, bin_octets = iputils.ip2bin("127.0.1") 97 | if not bin_ip then 98 | ngx.say(bin_octets) 99 | else 100 | ngx.say(bin_ip) 101 | end 102 | '; 103 | } 104 | --- request 105 | GET /a 106 | --- no_error_log 107 | [error] 108 | --- response_body 109 | Invalid IP 110 | 111 | 112 | === TEST 3a: ip2bin returns error on bad ip form 113 | --- http_config eval 114 | "$::HttpConfig" 115 | . q{ 116 | } 117 | --- config 118 | location /a { 119 | content_by_lua ' 120 | local iputils = require("resty.iputils") 121 | local bin_ip, bin_octets = iputils.ip2bin(12344567) 122 | if not bin_ip then 123 | ngx.say(bin_octets) 124 | else 125 | ngx.say(bin_ip) 126 | end 127 | '; 128 | } 129 | --- request 130 | GET /a 131 | --- no_error_log 132 | [error] 133 | --- response_body 134 | IP must be a string 135 | 136 | === TEST 3b: ip2bin returns error on bad ip form - octet out of bounds 137 | --- http_config eval 138 | "$::HttpConfig" 139 | . q{ 140 | } 141 | --- config 142 | location /a { 143 | content_by_lua ' 144 | local iputils = require("resty.iputils") 145 | local bin_ip, bin_octets = iputils.ip2bin("100.256.900.0") 146 | if not bin_ip then 147 | ngx.say(bin_octets) 148 | else 149 | ngx.say(bin_ip) 150 | end 151 | '; 152 | } 153 | --- request 154 | GET /a 155 | --- no_error_log 156 | [error] 157 | --- response_body 158 | Invalid octet: 256 159 | 160 | === TEST 3c: ip2bin returns error on bad ip form - string octet 161 | --- http_config eval 162 | "$::HttpConfig" 163 | . q{ 164 | } 165 | --- config 166 | location /a { 167 | content_by_lua ' 168 | local iputils = require("resty.iputils") 169 | local bin_ip, bin_octets = iputils.ip2bin("127.0.asdf.1") 170 | if not bin_ip then 171 | ngx.say(bin_octets) 172 | else 173 | ngx.say(bin_ip) 174 | end 175 | '; 176 | } 177 | --- request 178 | GET /a 179 | --- no_error_log 180 | [error] 181 | --- response_body 182 | Invalid octet: asdf 183 | 184 | === TEST 3d: ip2bin returns error on bad ip form - too many octets 185 | --- http_config eval 186 | "$::HttpConfig" 187 | . q{ 188 | } 189 | --- config 190 | location /a { 191 | content_by_lua ' 192 | local iputils = require("resty.iputils") 193 | local bin_ip, bin_octets = iputils.ip2bin("127.0.0.0.1") 194 | if not bin_ip then 195 | ngx.say(bin_octets) 196 | else 197 | ngx.say(bin_ip) 198 | end 199 | '; 200 | } 201 | --- request 202 | GET /a 203 | --- no_error_log 204 | [error] 205 | --- response_body 206 | Invalid IP 207 | 208 | === TEST 4: ip2bin returns binary representation of IP with lrucache 209 | --- http_config eval 210 | "$::HttpConfig" 211 | . q{ 212 | } 213 | --- config 214 | location /a { 215 | content_by_lua ' 216 | local iputils = require("resty.iputils") 217 | iputils.enable_lrucache(100) 218 | 219 | local bin_ip, bin_octets = iputils.ip2bin("127.0.0.1") 220 | ngx.say(bin_ip) 221 | local bin_ip, bin_octets = iputils.ip2bin("127.0.0.1") 222 | ngx.say(bin_ip) 223 | 224 | local bin_ip, bin_octets = iputils.ip2bin("10.0.0.1") 225 | ngx.say(bin_ip) 226 | '; 227 | } 228 | --- request 229 | GET /a 230 | --- no_error_log 231 | [error] 232 | --- response_body 233 | 2130706433 234 | 2130706433 235 | 167772161 236 | -------------------------------------------------------------------------------- /t/02-cidr.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | use Cwd qw(cwd); 3 | 4 | plan tests => repeat_each() * 21; 5 | 6 | my $pwd = cwd(); 7 | 8 | $ENV{TEST_LEDGE_REDIS_DATABASE} ||= 1; 9 | 10 | our $HttpConfig = qq{ 11 | lua_package_path "$pwd/lib/?.lua;;"; 12 | }; 13 | 14 | no_long_string(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | === TEST 1: parse_cidr returns lower and upper bounds of network 19 | --- http_config eval 20 | "$::HttpConfig" 21 | . q{ 22 | } 23 | --- config 24 | location /a { 25 | content_by_lua ' 26 | local iputils = require("resty.iputils") 27 | local lower, upper = iputils.parse_cidr("10.10.10.0/24") 28 | ngx.say(lower) 29 | ngx.say(upper) 30 | '; 31 | } 32 | --- request 33 | GET /a 34 | --- no_error_log 35 | [error] 36 | --- response_body 37 | 168430080 38 | 168430335 39 | 40 | === TEST 1b: parse_cidr returns unsigned lower and upper bounds of network 41 | --- http_config eval 42 | "$::HttpConfig" 43 | . q{ 44 | } 45 | --- config 46 | location /a { 47 | content_by_lua ' 48 | local iputils = require("resty.iputils") 49 | local lower, upper = iputils.parse_cidr("250.250.250.0/24") 50 | ngx.say(lower) 51 | ngx.say(upper) 52 | '; 53 | } 54 | --- request 55 | GET /a 56 | --- no_error_log 57 | [error] 58 | --- response_body 59 | 4210752000 60 | 4210752255 61 | 62 | === TEST 2: cidr with last octet in middle of range 63 | --- http_config eval 64 | "$::HttpConfig" 65 | . q{ 66 | } 67 | --- config 68 | location /a { 69 | content_by_lua ' 70 | local iputils = require("resty.iputils") 71 | local lower, upper = iputils.parse_cidr("10.10.10.123/24") 72 | ngx.say(lower) 73 | ngx.say(upper) 74 | '; 75 | } 76 | --- request 77 | GET /a 78 | --- no_error_log 79 | [error] 80 | --- response_body 81 | 168430080 82 | 168430335 83 | 84 | === TEST 3: cidr with no prefix, assume /32 85 | --- http_config eval 86 | "$::HttpConfig" 87 | . q{ 88 | } 89 | --- config 90 | location /a { 91 | content_by_lua ' 92 | local iputils = require("resty.iputils") 93 | local lower, upper = iputils.parse_cidr("10.10.10.123") 94 | ngx.say(lower) 95 | ngx.say(upper) 96 | '; 97 | } 98 | --- request 99 | GET /a 100 | --- no_error_log 101 | [error] 102 | --- response_body 103 | 168430203 104 | 168430203 105 | 106 | === TEST 4: cidr in bad form returns error message 107 | --- http_config eval 108 | "$::HttpConfig" 109 | . q{ 110 | } 111 | --- config 112 | location /a { 113 | content_by_lua ' 114 | local iputils = require("resty.iputils") 115 | local lower, upper = iputils.parse_cidr("10.10.10.300/24") 116 | ngx.say(lower) 117 | ngx.say(upper) 118 | '; 119 | } 120 | --- request 121 | GET /a 122 | --- no_error_log 123 | [error] 124 | --- response_body 125 | nil 126 | Invalid octet: 300 127 | 128 | === TEST 4a: cidr in bad form returns error message 129 | --- http_config eval 130 | "$::HttpConfig" 131 | . q{ 132 | } 133 | --- config 134 | location /a { 135 | content_by_lua ' 136 | local iputils = require("resty.iputils") 137 | local lower, upper = iputils.parse_cidr("10.10.10/24") 138 | ngx.say(lower) 139 | ngx.say(upper) 140 | '; 141 | } 142 | --- request 143 | GET /a 144 | --- no_error_log 145 | [error] 146 | --- response_body 147 | nil 148 | Invalid IP 149 | 150 | === TEST 4b: cidr in bad form returns error message 151 | --- http_config eval 152 | "$::HttpConfig" 153 | . q{ 154 | } 155 | --- config 156 | location /a { 157 | content_by_lua ' 158 | local iputils = require("resty.iputils") 159 | local lower, upper = iputils.parse_cidr("10.10.10.0/99") 160 | ngx.say(lower) 161 | ngx.say(upper) 162 | '; 163 | } 164 | --- request 165 | GET /a 166 | --- no_error_log 167 | [error] 168 | --- response_body 169 | nil 170 | Invalid prefix: /99 171 | -------------------------------------------------------------------------------- /t/03-misc.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | use Cwd qw(cwd); 3 | 4 | plan tests => repeat_each() * 30; 5 | 6 | my $pwd = cwd(); 7 | 8 | $ENV{TEST_LEDGE_REDIS_DATABASE} ||= 1; 9 | 10 | our $HttpConfig = qq{ 11 | lua_package_path "$pwd/lib/?.lua;;"; 12 | }; 13 | 14 | no_long_string(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | === TEST 1: parse_cidrs array of lower/upper arrays 19 | --- http_config eval 20 | "$::HttpConfig" 21 | . q{ 22 | } 23 | --- config 24 | location /a { 25 | content_by_lua ' 26 | local iputils = require("resty.iputils") 27 | local cidrs = { 28 | "10.10.10.0/24", 29 | "10.10.11.0/24", 30 | "10.10.12.1" 31 | } 32 | local parsed, err = iputils.parse_cidrs(cidrs) 33 | if not parsed then 34 | ngx.say(err) 35 | else 36 | for _, net in ipairs(parsed) do 37 | ngx.say(net[1], " ", net[2]) 38 | end 39 | end 40 | '; 41 | } 42 | --- request 43 | GET /a 44 | --- no_error_log 45 | [error] 46 | --- response_body 47 | 168430080 168430335 48 | 168430336 168430591 49 | 168430593 168430593 50 | 51 | === TEST 2: invalid CIDR logs error and are ignored 52 | --- http_config eval 53 | "$::HttpConfig" 54 | . q{ 55 | } 56 | --- config 57 | location /a { 58 | content_by_lua ' 59 | local iputils = require("resty.iputils") 60 | local cidrs = { 61 | "10.10.10.0/24", 62 | "10.10.20.0/40", 63 | "10.10.11.0/24", 64 | "300.0.0.0", 65 | "10.10.12.1" 66 | } 67 | local parsed, err = iputils.parse_cidrs(cidrs) 68 | if not parsed then 69 | ngx.say(err) 70 | else 71 | for _, net in ipairs(parsed) do 72 | ngx.say(net[1], " ", net[2]) 73 | end 74 | end 75 | '; 76 | } 77 | --- request 78 | GET /a 79 | --- error_log 80 | Error parsing '10.10.20.0/40': Invalid prefix: /40 81 | --- response_body 82 | 168430080 168430335 83 | 168430336 168430591 84 | 168430593 168430593 85 | 86 | === TEST 2a: invalid CIDR logs error and are ignored 87 | --- http_config eval 88 | "$::HttpConfig" 89 | . q{ 90 | } 91 | --- config 92 | location /a { 93 | content_by_lua ' 94 | local iputils = require("resty.iputils") 95 | local cidrs = { 96 | "10.10.10.0/24", 97 | "10.10.11.0/24", 98 | "300.0.0.0", 99 | "10.10.12.1" 100 | } 101 | local parsed, err = iputils.parse_cidrs(cidrs) 102 | if not parsed then 103 | ngx.say(err) 104 | else 105 | for _, net in ipairs(parsed) do 106 | ngx.say(net[1], " ", net[2]) 107 | end 108 | end 109 | '; 110 | } 111 | --- request 112 | GET /a 113 | --- error_log 114 | Error parsing '300.0.0.0': Invalid octet: 300 115 | --- response_body 116 | 168430080 168430335 117 | 168430336 168430591 118 | 168430593 168430593 119 | 120 | === TEST 3: ip_in_cidrs checks ip exists in array of parsed cidrs 121 | --- http_config eval 122 | "$::HttpConfig" 123 | . q{ 124 | } 125 | --- config 126 | location /a { 127 | content_by_lua ' 128 | local iputils = require("resty.iputils") 129 | local cidrs = { 130 | "10.10.10.0/24", 131 | "10.10.11.0/24", 132 | "10.10.12.1" 133 | } 134 | local parsed, err = iputils.parse_cidrs(cidrs) 135 | 136 | local pass, err = iputils.ip_in_cidrs("10.10.10.123", parsed) 137 | if pass == true then 138 | ngx.say("OK") 139 | elseif err then 140 | ngx.say(err) 141 | else 142 | ngx.say("FAIL") 143 | end 144 | '; 145 | } 146 | --- request 147 | GET /a 148 | --- no_error_log 149 | [error] 150 | --- response_body 151 | OK 152 | 153 | === TEST 3b: ip_in_cidrs checks ip exists in array of parsed cidrs, /32 matches 154 | --- http_config eval 155 | "$::HttpConfig" 156 | . q{ 157 | } 158 | --- config 159 | location /a { 160 | content_by_lua ' 161 | local iputils = require("resty.iputils") 162 | local cidrs = { 163 | "10.10.10.0/24", 164 | "10.10.11.0/24", 165 | "10.10.12.1" 166 | } 167 | local parsed, err = iputils.parse_cidrs(cidrs) 168 | 169 | local pass, err = iputils.ip_in_cidrs("10.10.12.1", parsed) 170 | if pass == true then 171 | ngx.say("OK") 172 | elseif err then 173 | ngx.say(err) 174 | else 175 | ngx.say("FAIL") 176 | end 177 | '; 178 | } 179 | --- request 180 | GET /a 181 | --- no_error_log 182 | [error] 183 | --- response_body 184 | OK 185 | 186 | === TEST 4a: invalid ip to ip_in_cidrs returns error 187 | --- http_config eval 188 | "$::HttpConfig" 189 | . q{ 190 | } 191 | --- config 192 | location /a { 193 | content_by_lua ' 194 | local iputils = require("resty.iputils") 195 | local cidrs = { 196 | "10.10.10.0/24", 197 | "10.10.11.0/24", 198 | "10.10.12.1" 199 | } 200 | local parsed, err = iputils.parse_cidrs(cidrs) 201 | 202 | local pass, err = iputils.ip_in_cidrs("10.10.12.400", parsed) 203 | if pass == true then 204 | ngx.say("OK") 205 | elseif err then 206 | ngx.say(err) 207 | else 208 | ngx.say("FAIL") 209 | end 210 | '; 211 | } 212 | --- request 213 | GET /a 214 | --- no_error_log 215 | [error] 216 | --- response_body 217 | Invalid octet: 400 218 | 219 | === TEST 5: binip_in_cidrs checks ip exists in array of parsed cidrs 220 | --- http_config eval 221 | "$::HttpConfig" 222 | . q{ 223 | } 224 | --- config 225 | location /a { 226 | content_by_lua ' 227 | local iputils = require("resty.iputils") 228 | local cidrs = { 229 | "127.0.0.0/24" 230 | } 231 | local parsed, err = iputils.parse_cidrs(cidrs) 232 | 233 | local pass, err = iputils.binip_in_cidrs(ngx.var.binary_remote_addr, parsed) 234 | if pass == true then 235 | ngx.say("OK") 236 | elseif err then 237 | ngx.say(err) 238 | else 239 | ngx.say("FAIL") 240 | end 241 | '; 242 | } 243 | --- request 244 | GET /a 245 | --- no_error_log 246 | [error] 247 | --- response_body 248 | OK 249 | 250 | === TEST 5a: binip_in_cidrs checks ip not exists in array of parsed cidrs 251 | --- http_config eval 252 | "$::HttpConfig" 253 | . q{ 254 | } 255 | --- config 256 | location /a { 257 | content_by_lua ' 258 | local iputils = require("resty.iputils") 259 | local cidrs = { 260 | "128.0.0.0/24" 261 | } 262 | local parsed, err = iputils.parse_cidrs(cidrs) 263 | 264 | local pass, err = iputils.binip_in_cidrs(ngx.var.binary_remote_addr, parsed) 265 | if pass == false then 266 | ngx.say("OK") 267 | elseif err then 268 | ngx.say(err) 269 | else 270 | ngx.say("FAIL") 271 | end 272 | '; 273 | } 274 | --- request 275 | GET /a 276 | --- no_error_log 277 | [error] 278 | --- response_body 279 | OK 280 | 281 | === TEST 5b: binip_in_cidrs checks ip exists in array of parsed cidrs 282 | --- http_config eval 283 | "$::HttpConfig" 284 | . q{ 285 | } 286 | --- config 287 | location /a { 288 | content_by_lua ' 289 | local iputils = require("resty.iputils") 290 | local cidrs = { 291 | "128.0.0.0/24", 292 | "127.0.0.1" 293 | } 294 | local parsed, err = iputils.parse_cidrs(cidrs) 295 | 296 | local pass, err = iputils.binip_in_cidrs(ngx.var.binary_remote_addr, parsed) 297 | if pass == true then 298 | ngx.say("OK") 299 | elseif err then 300 | ngx.say(err) 301 | else 302 | ngx.say("FAIL") 303 | end 304 | '; 305 | } 306 | --- request 307 | GET /a 308 | --- no_error_log 309 | [error] 310 | --- response_body 311 | OK 312 | 313 | === TEST 6: 0.0.0.0/0 CIDR parses corrected 314 | --- http_config eval 315 | "$::HttpConfig" 316 | . q{ 317 | } 318 | --- config 319 | location /a { 320 | content_by_lua_block { 321 | local iputils = require("resty.iputils") 322 | 323 | local iputils = require("resty.iputils") 324 | local lower, upper = iputils.parse_cidr("0.0.0.0/0") 325 | if not lower then 326 | ngx.say(upper) 327 | else 328 | ngx.say(lower, " ", upper) 329 | end 330 | 331 | 332 | local pass, err = iputils.ip_in_cidrs("10.10.12.100", {{lower, upper}}) 333 | if pass == true then 334 | ngx.say("OK") 335 | elseif err then 336 | ngx.say(err) 337 | else 338 | ngx.say("FAIL") 339 | end 340 | 341 | local pass, err = iputils.ip_in_cidrs("1.1.1.1", {{lower, upper}}) 342 | if pass == true then 343 | ngx.say("OK") 344 | elseif err then 345 | ngx.say(err) 346 | else 347 | ngx.say("FAIL") 348 | end 349 | local pass, err = iputils.ip_in_cidrs("0.0.0.0", {{lower, upper}}) 350 | if pass == true then 351 | ngx.say("OK") 352 | elseif err then 353 | ngx.say(err) 354 | else 355 | ngx.say("FAIL") 356 | end 357 | 358 | local pass, err = iputils.ip_in_cidrs("255.255.255.255", {{lower, upper}}) 359 | if pass == true then 360 | ngx.say("OK") 361 | elseif err then 362 | ngx.say(err) 363 | else 364 | ngx.say("FAIL") 365 | end 366 | } 367 | } 368 | --- request 369 | GET /a 370 | --- no_error_log 371 | [error] 372 | --- response_body 373 | 0 4294967295 374 | OK 375 | OK 376 | OK 377 | OK 378 | -------------------------------------------------------------------------------- /util/lua-releng.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | sub file_contains ($$); 7 | 8 | my $version; 9 | for my $file (map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua }) { 10 | 11 | 12 | print "Checking use of Lua global variables in file $file ...\n"; 13 | system("luac -p -l $file | grep ETGLOBAL | grep -vE 'require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|rawget|rawset|rawlen'"); 14 | file_contains($file, "attempt to write to undeclared variable"); 15 | #system("grep -H -n -E --color '.{81}' $file"); 16 | } 17 | 18 | sub file_contains ($$) { 19 | my ($file, $regex) = @_; 20 | open my $in, $file 21 | or die "Cannot open $file fo reading: $!\n"; 22 | my $content = do { local $/; <$in> }; 23 | close $in; 24 | #print "$content"; 25 | return scalar ($content =~ /$regex/); 26 | } 27 | 28 | if (-d 't') { 29 | for my $file (map glob, qw{ t/*.t t/*/*.t t/*/*/*.t }) { 30 | system(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $file}); 31 | } 32 | } 33 | --------------------------------------------------------------------------------