Merge commit '19515d9b26f2f4886ca117b91384509087f0ff3a' as 'src/deps/src/lua-resty-ipmatcher'
This commit is contained in:
commit
62e740a0bb
|
@ -0,0 +1,54 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- 'rockspec/**'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Lua
|
||||
uses: leafo/gh-actions-lua@v8
|
||||
|
||||
- name: Install Luarocks
|
||||
uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Extract release name
|
||||
id: release_env
|
||||
shell: bash
|
||||
run: |
|
||||
title="${{ github.event.head_commit.message }}"
|
||||
re="^feat: release v*(\S+)"
|
||||
if [[ $title =~ $re ]]; then
|
||||
v=v${BASH_REMATCH[1]}
|
||||
echo "##[set-output name=version;]${v}"
|
||||
echo "##[set-output name=version_withou_v;]${BASH_REMATCH[1]}"
|
||||
else
|
||||
echo "commit format is not correct"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.release_env.outputs.version }}
|
||||
release_name: ${{ steps.release_env.outputs.version }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload to luarocks
|
||||
env:
|
||||
LUAROCKS_TOKEN: ${{ secrets.LUAROCKS_TOKEN }}
|
||||
run: |
|
||||
luarocks install dkjson
|
||||
luarocks upload rockspec/lua-resty-ipmatcher-${{ steps.release_env.outputs.version_withou_v }}-0.rockspec --api-key=${LUAROCKS_TOKEN}
|
|
@ -0,0 +1,38 @@
|
|||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: "ubuntu-20.04"
|
||||
env:
|
||||
OPENRESTY_PREFIX: "/usr/local/openresty"
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Linux Get dependencies
|
||||
run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl luarocks
|
||||
|
||||
- name: Linux Before install
|
||||
run: |
|
||||
sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1)
|
||||
sudo luarocks install luacheck
|
||||
|
||||
- name: Linux Install
|
||||
run: |
|
||||
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
|
||||
sudo apt-get -y install software-properties-common
|
||||
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"
|
||||
sudo apt-get update
|
||||
sudo apt-get install openresty
|
||||
|
||||
- name: Linux Script
|
||||
run: |
|
||||
export PATH=$OPENRESTY_PREFIX/nginx/sbin:$PATH
|
||||
make test
|
|
@ -0,0 +1,44 @@
|
|||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
#
|
||||
t/servroot
|
||||
go
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,31 @@
|
|||
INST_PREFIX ?= /usr
|
||||
INST_LIBDIR ?= $(INST_PREFIX)/lib/lua/5.1
|
||||
INST_LUADIR ?= $(INST_PREFIX)/share/lua/5.1
|
||||
INSTALL ?= install
|
||||
|
||||
|
||||
### lint: Lint Lua source code
|
||||
.PHONY: lint
|
||||
lint:
|
||||
luacheck -q resty
|
||||
|
||||
### test: Run test suite. Use test=... for specific tests
|
||||
.PHONY: test
|
||||
test:
|
||||
TEST_NGINX_LOG_LEVEL=info \
|
||||
prove -I. -I../test-nginx/lib -r t/
|
||||
|
||||
|
||||
### install: Install the library to runtime
|
||||
.PHONY: install
|
||||
install:
|
||||
$(INSTALL) -d $(INST_LUADIR)/resty/
|
||||
$(INSTALL) resty/*.lua $(INST_LUADIR)/resty/
|
||||
|
||||
|
||||
### help: Show Makefile rules
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo Makefile rules:
|
||||
@echo
|
||||
@grep -E '^### [-A-Za-z0-9_]+:' Makefile | sed 's/###/ /'
|
|
@ -0,0 +1,128 @@
|
|||
# lua-resty-ipmatcher
|
||||
|
||||
High performance match IP address for OpenResty Lua.
|
||||
|
||||
## API
|
||||
|
||||
```lua
|
||||
local ipmatcher = require("resty.ipmatcher")
|
||||
local ip = ipmatcher.new({
|
||||
"127.0.0.1",
|
||||
"192.168.0.0/16",
|
||||
"::1",
|
||||
"fe80::/32",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
ngx.say(ip:match("192.168.1.100"))
|
||||
ngx.say(ip:match("::1"))
|
||||
```
|
||||
|
||||
## ipmatcher.new
|
||||
|
||||
`syntax: ok, err = ipmatcher.new(ips)`
|
||||
|
||||
The `ips` is a array table, like `{ip1, ip2, ip3, ...}`,
|
||||
each element in the array is a string IP address.
|
||||
|
||||
```lua
|
||||
local ip, err = ipmatcher.new({"127.0.0.1", "192.168.0.0/16"})
|
||||
```
|
||||
|
||||
Returns `nil` and error message if failed to create new `ipmatcher` instance.
|
||||
|
||||
It supports any CIDR format for IPv4 and IPv6.
|
||||
|
||||
```lua
|
||||
local ip, err = ipmatcher.new({
|
||||
"127.0.0.1", "192.168.0.0/16",
|
||||
"::1", "fe80::/16",
|
||||
})
|
||||
```
|
||||
|
||||
## ipmatcher.new_with_value
|
||||
|
||||
`syntax: matcher, err = ipmatcher.new_with_value(ips)`
|
||||
|
||||
The `ips` is a hash table, like `{[ip1] = val1, [ip2] = val2, ...}`,
|
||||
each key in the hash is a string IP address.
|
||||
|
||||
When the `matcher` is created by `new_with_value`, calling `match` or `match_bin`
|
||||
on it will return the corresponding value of matched CIDR range instead of `true`.
|
||||
|
||||
```lua
|
||||
local ip, err = ipmatcher.new_with_value({
|
||||
["127.0.0.1"] = {info = "a"},
|
||||
["192.168.0.0/16"] = {info = "b"},
|
||||
})
|
||||
local data, err = ip:match("192.168.0.1")
|
||||
print(data.info) -- the value is "b"
|
||||
```
|
||||
|
||||
Returns `nil` and error message if failed to create new `ipmatcher` instance.
|
||||
|
||||
It supports any CIDR format for IPv4 and IPv6.
|
||||
|
||||
```lua
|
||||
local ip, err = ipmatcher.new_with_value({
|
||||
["127.0.0.1"] = {info = "a"},
|
||||
["192.168.0.0/16"] = {info = "b"},
|
||||
["::1"] = 1,
|
||||
["fe80::/32"] = "xx",
|
||||
})
|
||||
```
|
||||
|
||||
If the ip address can be satified by multiple CIDR ranges, the returned value
|
||||
is undefined (depended on the internal implementation). For instance,
|
||||
|
||||
```lua
|
||||
local ip, err = ipmatcher.new_with_value({
|
||||
["192.168.0.1"] = {info = "a"},
|
||||
["192.168.0.0/16"] = {info = "b"},
|
||||
})
|
||||
local data, err = ip:match("192.168.0.1")
|
||||
print(data.info) -- the value can be "a" or "b"
|
||||
```
|
||||
|
||||
## ip.match
|
||||
|
||||
`syntax: ok, err = ip:match(ip)`
|
||||
|
||||
Returns a `true` if the IP exists within any of the specified IP list.
|
||||
Returns a `false` if the IP doesn't exist within any of the specified IP list.
|
||||
Returns `false` and an error message with an invalid IP address.
|
||||
|
||||
```lua
|
||||
local ok, err = ip:match("127.0.0.1")
|
||||
```
|
||||
|
||||
## ip.match_bin
|
||||
|
||||
`syntax: ok, err = ip:match_bin(bin_ip)`
|
||||
|
||||
Returns a `true` if the binary format IP exists within any of the specified IP list.
|
||||
|
||||
Returns `nil` and an error message with an invalid binary IP address.
|
||||
|
||||
```lua
|
||||
local ok, err = ip:match_bin(ngx.var.binary_remote_addr)
|
||||
```
|
||||
|
||||
## ipmatcher.parse_ipv4
|
||||
|
||||
`syntax: res = ipmatcher.parse_ipv4(ip)`
|
||||
|
||||
Tries to parse an IPv4 address to a host byte order FFI uint32_t type integer.
|
||||
|
||||
Returns a `false` if the ip is not a valid IPv4 address.
|
||||
|
||||
|
||||
## ipmatcher.parse_ipv6
|
||||
|
||||
`syntax: res = ipmatcher.parse_ipv6(ip)`
|
||||
|
||||
Tries to parse an IPv6 address to a table with four host byte order FFI uint32_t
|
||||
type integer. The given IPv6 address can be wrapped by square brackets
|
||||
like `[::1]`.
|
||||
|
||||
Returns a `false` if the ip is not a valid IPv6 address.
|
|
@ -0,0 +1,407 @@
|
|||
local base = require("resty.core.base")
|
||||
local bit = require("bit")
|
||||
local clear_tab = require("table.clear")
|
||||
local nkeys = require("table.nkeys")
|
||||
local new_tab = base.new_tab
|
||||
local find_str = string.find
|
||||
local tonumber = tonumber
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local ffi = require "ffi"
|
||||
local ffi_cdef = ffi.cdef
|
||||
local ffi_copy = ffi.copy
|
||||
local ffi_new = ffi.new
|
||||
local C = ffi.C
|
||||
local insert_tab = table.insert
|
||||
local sort_tab = table.sort
|
||||
local string = string
|
||||
local setmetatable=setmetatable
|
||||
local type = type
|
||||
local error = error
|
||||
local str_sub = string.sub
|
||||
local str_byte = string.byte
|
||||
local cur_level = ngx.config.subsystem == "http" and
|
||||
require "ngx.errlog" .get_sys_filter_level()
|
||||
|
||||
local AF_INET = 2
|
||||
local AF_INET6 = 10
|
||||
if ffi.os == "OSX" then
|
||||
AF_INET6 = 30
|
||||
elseif ffi.os == "BSD" then
|
||||
AF_INET6 = 28
|
||||
elseif ffi.os == "Windows" then
|
||||
AF_INET6 = 23
|
||||
end
|
||||
|
||||
|
||||
local _M = {_VERSION = 0.3}
|
||||
|
||||
|
||||
ffi_cdef[[
|
||||
int inet_pton(int af, const char * restrict src, void * restrict dst);
|
||||
uint32_t ntohl(uint32_t netlong);
|
||||
]]
|
||||
|
||||
|
||||
local parse_ipv4
|
||||
do
|
||||
local inet = ffi_new("unsigned int [1]")
|
||||
|
||||
function parse_ipv4(ip)
|
||||
if not ip then
|
||||
return false
|
||||
end
|
||||
|
||||
if C.inet_pton(AF_INET, ip, inet) ~= 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
return C.ntohl(inet[0])
|
||||
end
|
||||
end
|
||||
_M.parse_ipv4 = parse_ipv4
|
||||
|
||||
local parse_bin_ipv4
|
||||
do
|
||||
local inet = ffi_new("unsigned int [1]")
|
||||
|
||||
function parse_bin_ipv4(ip)
|
||||
if not ip or #ip ~= 4 then
|
||||
return false
|
||||
end
|
||||
|
||||
ffi_copy(inet, ip, 4)
|
||||
return C.ntohl(inet[0])
|
||||
end
|
||||
end
|
||||
|
||||
local parse_ipv6
|
||||
do
|
||||
local inets = ffi_new("unsigned int [4]")
|
||||
|
||||
function parse_ipv6(ip)
|
||||
if not ip then
|
||||
return false
|
||||
end
|
||||
|
||||
if str_byte(ip, 1, 1) == str_byte('[')
|
||||
and str_byte(ip, #ip) == str_byte(']') then
|
||||
|
||||
-- strip square brackets around IPv6 literal if present
|
||||
ip = str_sub(ip, 2, #ip - 1)
|
||||
end
|
||||
|
||||
if C.inet_pton(AF_INET6, ip, inets) ~= 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local inets_arr = new_tab(4, 0)
|
||||
for i = 0, 3 do
|
||||
insert_tab(inets_arr, C.ntohl(inets[i]))
|
||||
end
|
||||
return inets_arr
|
||||
end
|
||||
end
|
||||
_M.parse_ipv6 = parse_ipv6
|
||||
|
||||
local parse_bin_ipv6
|
||||
do
|
||||
local inets = ffi_new("unsigned int [4]")
|
||||
|
||||
function parse_bin_ipv6(ip)
|
||||
if not ip or #ip ~= 16 then
|
||||
return false
|
||||
end
|
||||
|
||||
ffi_copy(inets, ip, 16)
|
||||
local inets_arr = new_tab(4, 0)
|
||||
for i = 0, 3 do
|
||||
insert_tab(inets_arr, C.ntohl(inets[i]))
|
||||
end
|
||||
return inets_arr
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local mt = {__index = _M}
|
||||
|
||||
|
||||
local ngx_log = ngx.log
|
||||
local ngx_INFO = ngx.INFO
|
||||
local function log_info(...)
|
||||
if cur_level and ngx_INFO > cur_level then
|
||||
return
|
||||
end
|
||||
|
||||
return ngx_log(ngx_INFO, ...)
|
||||
end
|
||||
|
||||
|
||||
local function split_ip(ip_addr_org)
|
||||
local idx = find_str(ip_addr_org, "/", 1, true)
|
||||
if not idx then
|
||||
return ip_addr_org
|
||||
end
|
||||
|
||||
local ip_addr = str_sub(ip_addr_org, 1, idx - 1)
|
||||
local ip_addr_mask = str_sub(ip_addr_org, idx + 1)
|
||||
return ip_addr, tonumber(ip_addr_mask)
|
||||
end
|
||||
_M.split_ip = split_ip
|
||||
|
||||
|
||||
local idxs = {}
|
||||
local function gen_ipv6_idxs(inets_ipv6, mask)
|
||||
clear_tab(idxs)
|
||||
|
||||
for _, inet in ipairs(inets_ipv6) do
|
||||
local valid_mask = mask
|
||||
if valid_mask > 32 then
|
||||
valid_mask = 32
|
||||
end
|
||||
|
||||
if valid_mask == 32 then
|
||||
insert_tab(idxs, inet)
|
||||
else
|
||||
insert_tab(idxs, bit.rshift(inet, 32 - valid_mask))
|
||||
end
|
||||
|
||||
mask = mask - 32
|
||||
if mask <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return idxs
|
||||
end
|
||||
|
||||
|
||||
local function cmp(x, y)
|
||||
return x > y
|
||||
end
|
||||
|
||||
|
||||
local function new(ips, with_value)
|
||||
if not ips or type(ips) ~= "table" then
|
||||
error("missing valid ip argument", 2)
|
||||
end
|
||||
|
||||
local parsed_ipv4s = {}
|
||||
local parsed_ipv4s_mask = {}
|
||||
local ipv4_match_all_value
|
||||
|
||||
local parsed_ipv6s = {}
|
||||
local parsed_ipv6s_mask = {}
|
||||
local ipv6_values = {}
|
||||
local ipv6s_values_idx = 1
|
||||
local ipv6_match_all_value
|
||||
|
||||
local iter = with_value and pairs or ipairs
|
||||
for a, b in iter(ips) do
|
||||
local ip_addr_org, value
|
||||
if with_value then
|
||||
ip_addr_org = a
|
||||
value = b
|
||||
|
||||
else
|
||||
ip_addr_org = b
|
||||
value = true
|
||||
end
|
||||
|
||||
local ip_addr, ip_addr_mask = split_ip(ip_addr_org)
|
||||
|
||||
local inet_ipv4 = parse_ipv4(ip_addr)
|
||||
if inet_ipv4 then
|
||||
ip_addr_mask = ip_addr_mask or 32
|
||||
if ip_addr_mask == 32 then
|
||||
parsed_ipv4s[inet_ipv4] = value
|
||||
|
||||
elseif ip_addr_mask == 0 then
|
||||
ipv4_match_all_value = value
|
||||
|
||||
else
|
||||
local valid_inet_addr = bit.rshift(inet_ipv4, 32 - ip_addr_mask)
|
||||
|
||||
parsed_ipv4s_mask[ip_addr_mask] = parsed_ipv4s_mask[ip_addr_mask] or {}
|
||||
parsed_ipv4s_mask[ip_addr_mask][valid_inet_addr] = value
|
||||
log_info("ipv4 mask: ", ip_addr_mask,
|
||||
" valid inet: ", valid_inet_addr)
|
||||
end
|
||||
|
||||
goto continue
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_ipv6(ip_addr)
|
||||
if inets_ipv6 then
|
||||
ip_addr_mask = ip_addr_mask or 128
|
||||
if ip_addr_mask == 128 then
|
||||
parsed_ipv6s[ip_addr] = value
|
||||
|
||||
elseif ip_addr_mask == 0 then
|
||||
ipv6_match_all_value = value
|
||||
end
|
||||
|
||||
parsed_ipv6s[ip_addr_mask] = parsed_ipv6s[ip_addr_mask] or {}
|
||||
|
||||
local inets_idxs = gen_ipv6_idxs(inets_ipv6, ip_addr_mask)
|
||||
local node = parsed_ipv6s[ip_addr_mask]
|
||||
for i, inet in ipairs(inets_idxs) do
|
||||
if i == #inets_idxs then
|
||||
if with_value then
|
||||
ipv6_values[ipv6s_values_idx] = value
|
||||
node[inet] = ipv6s_values_idx
|
||||
ipv6s_values_idx = ipv6s_values_idx + 1
|
||||
else
|
||||
node[inet] = true
|
||||
end
|
||||
end
|
||||
node[inet] = node[inet] or {}
|
||||
node = node[inet]
|
||||
end
|
||||
|
||||
parsed_ipv6s_mask[ip_addr_mask] = true
|
||||
|
||||
goto continue
|
||||
end
|
||||
|
||||
if not inet_ipv4 and not inets_ipv6 then
|
||||
return nil, "invalid ip address: " .. ip_addr
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
local ipv4_mask_arr = new_tab(nkeys(parsed_ipv4s_mask), 0)
|
||||
local i = 1
|
||||
for k, _ in pairs(parsed_ipv4s_mask) do
|
||||
ipv4_mask_arr[i] = k
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
sort_tab(ipv4_mask_arr, cmp)
|
||||
|
||||
local ipv6_mask_arr = new_tab(nkeys(parsed_ipv6s_mask), 0)
|
||||
|
||||
i = 1
|
||||
for k, _ in pairs(parsed_ipv6s_mask) do
|
||||
ipv6_mask_arr[i] = k
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
sort_tab(ipv6_mask_arr, cmp)
|
||||
|
||||
return setmetatable({
|
||||
ipv4 = parsed_ipv4s,
|
||||
ipv4_mask = parsed_ipv4s_mask,
|
||||
ipv4_mask_arr = ipv4_mask_arr,
|
||||
ipv4_match_all_value = ipv4_match_all_value,
|
||||
|
||||
ipv6 = parsed_ipv6s,
|
||||
ipv6_mask = parsed_ipv6s_mask,
|
||||
ipv6_mask_arr = ipv6_mask_arr,
|
||||
ipv6_values = ipv6_values,
|
||||
ipv6_match_all_value = ipv6_match_all_value,
|
||||
}, mt)
|
||||
end
|
||||
|
||||
function _M.new(ips)
|
||||
return new(ips, false)
|
||||
end
|
||||
|
||||
function _M.new_with_value(ips)
|
||||
return new(ips, true)
|
||||
end
|
||||
|
||||
|
||||
local function match_ipv4(self, ip)
|
||||
local ipv4s = self.ipv4
|
||||
local value = ipv4s[ip]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
local ipv4_mask = self.ipv4_mask
|
||||
if self.ipv4_match_all_value ~= nil then
|
||||
return self.ipv4_match_all_value -- match any ip
|
||||
end
|
||||
|
||||
for _, mask in ipairs(self.ipv4_mask_arr) do
|
||||
local valid_inet_addr = bit.rshift(ip, 32 - mask)
|
||||
|
||||
log_info("ipv4 mask: ", mask,
|
||||
" valid inet: ", valid_inet_addr)
|
||||
|
||||
value = ipv4_mask[mask][valid_inet_addr]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function match_ipv6(self, ip)
|
||||
local ipv6s = self.ipv6
|
||||
if self.ipv6_match_all_value ~= nil then
|
||||
return self.ipv6_match_all_value -- match any ip
|
||||
end
|
||||
|
||||
for _, mask in ipairs(self.ipv6_mask_arr) do
|
||||
local node = ipv6s[mask]
|
||||
local inet_idxs = gen_ipv6_idxs(ip, mask)
|
||||
for _, inet in ipairs(inet_idxs) do
|
||||
if not node[inet] then
|
||||
break
|
||||
else
|
||||
node = node[inet]
|
||||
if node == true then
|
||||
return true
|
||||
end
|
||||
if type(node) == "number" then
|
||||
-- fetch with the ipv6s_values_idx
|
||||
return self.ipv6_values[node]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function _M.match(self, ip)
|
||||
local inet_ipv4 = parse_ipv4(ip)
|
||||
if inet_ipv4 then
|
||||
return match_ipv4(self, inet_ipv4)
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_ipv6(ip)
|
||||
if not inets_ipv6 then
|
||||
return false, "invalid ip address, not ipv4 and ipv6"
|
||||
end
|
||||
|
||||
local ipv6s = self.ipv6
|
||||
local value = ipv6s[ip]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
return match_ipv6(self, inets_ipv6)
|
||||
end
|
||||
|
||||
|
||||
function _M.match_bin(self, bin_ip)
|
||||
local inet_ipv4 = parse_bin_ipv4(bin_ip)
|
||||
if inet_ipv4 then
|
||||
return match_ipv4(self, inet_ipv4)
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_bin_ipv6(bin_ip)
|
||||
if not inets_ipv6 then
|
||||
return false, "invalid ip address, not ipv4 and ipv6"
|
||||
end
|
||||
|
||||
return match_ipv6(self, inets_ipv6)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
|
@ -0,0 +1,21 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.2-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
tag = "v0.2",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for OpenResty Lua.",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.3-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
tag = "v0.3",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for OpenResty Lua.",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.4-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
tag = "v0.4",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for Lua(OpenResty).",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.5-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
tag = "v0.5",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for Lua(OpenResty).",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.6-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
tag = "v0.6",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for Lua(OpenResty).",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "0.6.1-0"
|
||||
source = {
|
||||
url = "git://github.com/api7/lua-resty-ipmatcher",
|
||||
tag = "v0.6.1",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for Lua(OpenResty).",
|
||||
homepage = "https://github.com/api7/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package = "lua-resty-ipmatcher"
|
||||
version = "master-0"
|
||||
source = {
|
||||
url = "git://github.com/iresty/lua-resty-ipmatcher",
|
||||
branch = "master",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "High performance match IP address for Lua(OpenResty).",
|
||||
homepage = "https://github.com/iresty/lua-resty-ipmatcher",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Yuansheng Wang <membphis@gmail.com>"
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.ipmatcher"] = "resty/ipmatcher.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package t::IP;
|
||||
|
||||
use lib 'lib';
|
||||
|
||||
use Test::Nginx::Socket::Lua::Stream -Base;
|
||||
|
||||
repeat_each(2);
|
||||
log_level('info');
|
||||
no_long_string();
|
||||
no_shuffle();
|
||||
|
||||
my $pwd = `pwd`;
|
||||
chomp $pwd;
|
||||
|
||||
add_block_preprocessor(sub {
|
||||
my ($block) = @_;
|
||||
|
||||
my $http_config = $block->http_config // '';
|
||||
$http_config = <<_EOC_;
|
||||
lua_package_path '$pwd/t/lib/?.lua;$pwd/?.lua;\$prefix/?.lua;;';
|
||||
lua_package_cpath '$pwd/?.so;;';
|
||||
|
||||
$http_config
|
||||
_EOC_
|
||||
|
||||
$block->set_value("http_config", $http_config);
|
||||
});
|
||||
|
||||
1;
|
|
@ -0,0 +1,85 @@
|
|||
local ngx_null = ngx.null
|
||||
local tostring = tostring
|
||||
local gsub = string.gsub
|
||||
local sort = table.sort
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local concat = table.concat
|
||||
local type = type
|
||||
local new_tab = require("table.new")
|
||||
|
||||
local _M = {version = 0.1}
|
||||
|
||||
local metachars = {
|
||||
['\t'] = '\\t',
|
||||
["\\"] = "\\\\",
|
||||
['"'] = '\\"',
|
||||
['\r'] = '\\r',
|
||||
['\n'] = '\\n',
|
||||
}
|
||||
|
||||
local function encode_str(s)
|
||||
-- XXX we will rewrite this when string.buffer is implemented
|
||||
-- in LuaJIT 2.1 because string.gsub cannot be JIT compiled.
|
||||
return gsub(s, '["\\\r\n\t]', metachars)
|
||||
end
|
||||
|
||||
local function is_arr(t)
|
||||
local exp = 1
|
||||
for k, _ in pairs(t) do
|
||||
if k ~= exp then
|
||||
return nil
|
||||
end
|
||||
exp = exp + 1
|
||||
end
|
||||
return exp - 1
|
||||
end
|
||||
|
||||
local encode
|
||||
|
||||
encode = function (v)
|
||||
if v == nil or v == ngx_null then
|
||||
return "null"
|
||||
end
|
||||
|
||||
local typ = type(v)
|
||||
if typ == 'string' then
|
||||
return '"' .. encode_str(v) .. '"'
|
||||
end
|
||||
|
||||
if typ == 'number' or typ == 'boolean' then
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
if typ == 'table' then
|
||||
local n = is_arr(v)
|
||||
if n then
|
||||
local bits = new_tab(n, 0)
|
||||
for i, elem in ipairs(v) do
|
||||
bits[i] = encode(elem)
|
||||
end
|
||||
return "[" .. concat(bits, ",") .. "]"
|
||||
end
|
||||
|
||||
local keys = {}
|
||||
local i = 0
|
||||
for key, _ in pairs(v) do
|
||||
i = i + 1
|
||||
keys[i] = key
|
||||
end
|
||||
sort(keys)
|
||||
|
||||
local bits = new_tab(0, i)
|
||||
i = 0
|
||||
for _, key in ipairs(keys) do
|
||||
i = i + 1
|
||||
bits[i] = encode(key) .. ":" .. encode(v[key])
|
||||
end
|
||||
return "{" .. concat(bits, ",") .. "}"
|
||||
end
|
||||
|
||||
return '"<' .. typ .. '>"'
|
||||
end
|
||||
_M.encode = encode
|
||||
|
||||
return _M
|
|
@ -0,0 +1,491 @@
|
|||
# vim:set ft= ts=4 sw=4 et fdm=marker:
|
||||
|
||||
use t::IP 'no_plan';
|
||||
|
||||
repeat_each(1);
|
||||
run_tests();
|
||||
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: ipv4 address
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"127.0.0.1",
|
||||
"127.0.0.2",
|
||||
"192.168.0.0/16",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
ngx.say(ip:match("127.0.0.2"))
|
||||
ngx.say(ip:match("127.0.0.3"))
|
||||
ngx.say(ip:match("192.168.1.1"))
|
||||
ngx.say(ip:match("192.168.1.100"))
|
||||
ngx.say(ip:match("192.100.1.100"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
true
|
||||
false
|
||||
true
|
||||
true
|
||||
false
|
||||
|
||||
|
||||
|
||||
=== TEST 2: ipv6 address
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"::1",
|
||||
"fe80::/32",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("::1"))
|
||||
ngx.say(ip:match("::2"))
|
||||
ngx.say(ip:match("fe80::"))
|
||||
ngx.say(ip:match("fe80:1::"))
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
false
|
||||
true
|
||||
false
|
||||
false
|
||||
|
||||
|
||||
|
||||
=== TEST 3: invalid ip address
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip, err = require("resty.ipmatcher").new({
|
||||
"127.0.0.ffff",
|
||||
})
|
||||
|
||||
ngx.say("ip: ", ip)
|
||||
ngx.say("err:", err)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
ip: nil
|
||||
err:invalid ip address: 127.0.0.ffff
|
||||
|
||||
|
||||
|
||||
=== TEST 4: invalid ip address
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"127.0.0.1",
|
||||
})
|
||||
|
||||
local ok, err = ip:match("127.0.0.ffff")
|
||||
ngx.say("ok: ", ok)
|
||||
ngx.say("err:", err)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
ok: false
|
||||
err:invalid ip address, not ipv4 and ipv6
|
||||
|
||||
|
||||
|
||||
=== TEST 5: ipv6 address (short mask)
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"fe80::/8",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("fe81::"))
|
||||
ngx.say(ip:match("ff80::"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
false
|
||||
|
||||
|
||||
|
||||
=== TEST 6: parse ipv6 address
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local cases = {
|
||||
{ip = "127.0.0.ffff"},
|
||||
{ip = ""},
|
||||
{ip = "["},
|
||||
{ip = "[]"},
|
||||
{ip = "[:1:]"},
|
||||
{ip = "[::1x"},
|
||||
{ip = "127.0.0.1"},
|
||||
}
|
||||
for _, case in ipairs(cases) do
|
||||
local valid = require("resty.ipmatcher").parse_ipv6(case.ip)
|
||||
if valid then
|
||||
ngx.log(ngx.ERR, "expect invalid IPv6 ", case.ip)
|
||||
end
|
||||
end
|
||||
|
||||
local cases = {
|
||||
{ip = "::1"},
|
||||
{ip = "[::1]"},
|
||||
{ip = "ff80::"},
|
||||
}
|
||||
for _, case in ipairs(cases) do
|
||||
local valid = require("resty.ipmatcher").parse_ipv6(case.ip)
|
||||
if not valid then
|
||||
ngx.log(ngx.ERR, "expect IPv6 ", case.ip)
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 7: match binary ip
|
||||
This test requires building Nginx with --with-http_realip_module
|
||||
--- config
|
||||
location /foo {
|
||||
set_real_ip_from 127.0.0.1;
|
||||
content_by_lua_block {
|
||||
ngx.log(ngx.INFO, ngx.var.http_x_real_ip, " ", ngx.var.binary_remote_addr)
|
||||
ngx.print(ngx.var.binary_remote_addr)
|
||||
}
|
||||
}
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local function get_bin_ip(ip)
|
||||
local sock = ngx.socket.tcp()
|
||||
sock:settimeout(500)
|
||||
|
||||
local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to connect: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\nX-Real-IP:" .. ip .. "\r\n\r\n"
|
||||
local bytes, err = sock:send(req)
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send http request: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
-- skip http header
|
||||
while true do
|
||||
local data, err, _ = sock:receive('*l')
|
||||
if err then
|
||||
ngx.log(ngx.ERR, 'unexpected error occurs when receiving http head: ' .. err)
|
||||
return
|
||||
end
|
||||
if #data == 0 then -- read last line of head
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local data, err = sock:receive('*a')
|
||||
sock:close()
|
||||
if not data then
|
||||
ngx.log(ngx.ERR, "failed to receive body: ", err)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"127.0.0.1",
|
||||
"192.168.0.0/16",
|
||||
"::1",
|
||||
"fe80::/8",
|
||||
})
|
||||
local cases = {
|
||||
{ip = "127.0.0.1", matched = true},
|
||||
{ip = "127.0.0.2", matched = false},
|
||||
{ip = "192.168.0.22", matched = true},
|
||||
{ip = "182.168.0.22", matched = false},
|
||||
{ip = "::1", matched = true},
|
||||
{ip = "::2", matched = false},
|
||||
{ip = "fe80::1", matched = true},
|
||||
}
|
||||
for _, case in ipairs(cases) do
|
||||
local res = ip:match_bin(get_bin_ip(case.ip))
|
||||
if res ~= case.matched then
|
||||
ngx.say("unexpected result for ", case.ip)
|
||||
end
|
||||
end
|
||||
ngx.say("ok")
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
ok
|
||||
|
||||
|
||||
|
||||
=== TEST 8: zero mask
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"::/0",
|
||||
"0.0.0.0/0",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
ngx.say(ip:match("0.0.0.0"))
|
||||
ngx.say(ip:match("fe81::"))
|
||||
ngx.say(ip:match("ff80::"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
|
||||
|
||||
|
||||
=== TEST 9: ipv6 special notation
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||
})
|
||||
ngx.say(ip:match("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
|
||||
-- zero in some occasions can be omitted
|
||||
ngx.say(ip:match("2001:db8:85a3:0:0:8a2e:370:7334"))
|
||||
ngx.say(ip:match("2001:db8:85a3::8a2e:370:7334"))
|
||||
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"::ffff:192.0.2.128",
|
||||
})
|
||||
ngx.say(ip:match("::ffff:192.0.2.128"))
|
||||
-- ipv4-mapped address
|
||||
ngx.say(ip:match("::ffff:c000:0280"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
|
||||
|
||||
|
||||
=== TEST 10: match with new_with_value
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new_with_value({
|
||||
["127.0.0.1"] = 1,
|
||||
["192.168.0.0/16"] = "2",
|
||||
["::1"] = 3,
|
||||
["fe80::/32"] = {value = 4},
|
||||
["fe81::/32"] = false,
|
||||
})
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
ngx.say(ip:match("127.0.0.2"))
|
||||
ngx.say(ip:match("192.168.1.1"))
|
||||
ngx.say(ip:match("192.168.1.100"))
|
||||
ngx.say(ip:match("192.100.1.100"))
|
||||
ngx.say(ip:match("::1"))
|
||||
ngx.say(ip:match("::2"))
|
||||
ngx.say(ip:match("fe80::").value)
|
||||
ngx.say(ip:match("fe80:1::"))
|
||||
ngx.say(ip:match("fe81::"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
1
|
||||
false
|
||||
2
|
||||
2
|
||||
false
|
||||
3
|
||||
false
|
||||
4
|
||||
false
|
||||
false
|
||||
|
||||
|
||||
|
||||
=== TEST 11: new_with_value and zero mask
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new_with_value({
|
||||
["::/0"] = 1,
|
||||
["0.0.0.0/0"] = 2,
|
||||
})
|
||||
|
||||
ngx.say(ip:match("127.0.0.1"))
|
||||
ngx.say(ip:match("0.0.0.0"))
|
||||
ngx.say(ip:match("fe81::"))
|
||||
ngx.say(ip:match("ff80::"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
2
|
||||
2
|
||||
1
|
||||
1
|
||||
|
||||
|
||||
|
||||
=== TEST 12: bug: ipv4 address overrided with the same mask
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
"192.168.0.0/16",
|
||||
"192.0.0.0/16",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("192.168.1.1"))
|
||||
ngx.say(ip:match("192.0.1.100"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
true
|
||||
|
||||
|
||||
|
||||
=== TEST 13: bug fixing: same ipv6 prefix with the same mask
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new({
|
||||
'2409:8928:6a00::/39',
|
||||
'2409:8928:a000::/39', -- 2409:8928:a000:: - 2409:8928:a1ff:ffff:ffff:ffff:ffff:ffff
|
||||
})
|
||||
|
||||
ngx.say(ip:match("2409:8928:6a00:2a57:1:1:d823:4521"))
|
||||
ngx.say(ip:match("2409:8928:6a01::"))
|
||||
ngx.say(ip:match("2409:8928:a0f8:2a57:1:1:d823:4521"))
|
||||
ngx.say(ip:match("2409:8928:a100::"))
|
||||
ngx.say(ip:match("2409:8928:a200::"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
false
|
||||
|
||||
|
||||
|
||||
=== TEST 14: accurate match with new_with_value
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local ip = require("resty.ipmatcher").new_with_value({
|
||||
["192.168.1.1/24"] = "level3",
|
||||
["192.168.1.1/16"] = "level2",
|
||||
["192.168.1.1/8"] = "level1",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("192.168.1.2"))
|
||||
ngx.say(ip:match("192.168.2.2"))
|
||||
ngx.say(ip:match("192.2.2.2"))
|
||||
|
||||
local ip = require("resty.ipmatcher").new_with_value({
|
||||
["192.168.1.1/16"] = "level2",
|
||||
["192.168.1.1/8"] = "level1",
|
||||
["192.168.1.1/24"] = "level3",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("192.168.1.2"))
|
||||
ngx.say(ip:match("192.168.2.2"))
|
||||
ngx.say(ip:match("192.2.2.2"))
|
||||
|
||||
local ip = require("resty.ipmatcher").new_with_value({
|
||||
["192.168.1.1/8"] = "level1",
|
||||
["192.168.1.1/16"] = "level2",
|
||||
["192.168.1.1/24"] = "level3",
|
||||
})
|
||||
|
||||
ngx.say(ip:match("192.168.1.2"))
|
||||
ngx.say(ip:match("192.168.2.2"))
|
||||
ngx.say(ip:match("192.2.2.2"))
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
level3
|
||||
level2
|
||||
level1
|
||||
level3
|
||||
level2
|
||||
level1
|
||||
level3
|
||||
level2
|
||||
level1
|
Loading…
Reference in New Issue