ngx嵌套lua时处理CIDR表示的IP地址段
背景
前置网关采用的是ngx中调用lua代码块的形式实现的功能扩展。
在网关开发过程中有很多的waf功能,比较常见就包括黑白名单的功能。为了批量的对某个IP段进行屏蔽,在配置时可能会以CIDR的形式配置IP段
结合lua代码进行处理
计算思路
我们查看某个IP是否在某个用CIDR表示的IP段时,只需要算出IP段的网络地址(主机位全部置0)作为最小值,计算IP段的广播地址(主机位全部置1)作为最大值即可即可判断当前IP是否在段中
运算过程
这里要用到位运算,由于 Lua 脚本语言本身不支持对数字的二进制操作,MUSHclient 为此提供了一套专门用于二进制操作的函数
bit.band 按位“与”运算
bit.bor 按位“或”运算
bit.lshift(a,b) a向左偏移到b位 相当于<<
bit.rshift(a,b) a逻辑右偏移到b位 相当于>>
local function unsign(n)
if n < 0 then
n = 4294967296 + n
end
return n
end
local function matchIp(source_ip_value, mask, target_ip_value)
local band = bit.band
local bor = bit.bor
local lshift = bit.lshift
local rshift = bit.rshift
local mask_val = band(lshift(0xFFFFFFFF ,32 - mask) , 0xFFFFFFFF) --这里将前mask置位1 后32-mask置为0 --> 111...1110000
local rmask_val = rshift(0xFFFFFFFF ,mask) --这里将前mask置位0 后32-mask置为1 --> 000...0001111
local min = unsign(band(source_ip_value,mask_val)) --将source_ip_value的32-mask置为0
local max = unsign(bor(source_ip_value,rmask_val)) --将source_ip_value的32-mask置为1
if mask == 32 then --特殊处理极端情况
min = source_ip_value
max = source_ip_value
end
if target_ip_value <= max and target_ip_value >= min then
return true
end
end
这里可能有疑问source_ip_value哪来的,这里就涉及到如何将IP字符串转换成数值
lua代码 这里使用了C函数,一般会放在其他文件中
比如util/iptoint.lua文件
local ffi = require "ffi"
local C = ffi.C
ffi.cdef[[
uint32_t ntohl(int netint);
int inet_addr(const char *ip);
]]
local function iptoint(ip)
result = C.ntohl(C.inet_addr(ip))
return result
end
return iptoint
使用:iptoint(ngx.var.remote_addr)
这里用到了两个C函数:
ntohl()将一个无符号长整形数从网络字节顺序转换为主机字节顺序
inet_addr()一个ip地址字符串转换成一个整数值,一般是'a.b.c.d'分成四段
作为对比这里贴出Go的生成代码。
iptoint的go实现,而且相当简单,net包已经实现了大部分功能。
func ip2uint(ipstr string) uint32 {
ip := net.ParseIP(ipstr)
if ip == nil {
return 0
}
ip = ip.To4()
return binary.BigEndian.Uint32(ip)
}
这样就可以在ngx的lua中实现黑白名单段的功能了