furt/src/ip_utils.lua

118 lines
3.2 KiB
Lua
Raw Normal View History

-- furt-lua/src/ip_utils.lua
-- IP address and CIDR utilities
-- Dragons@Work Digital Sovereignty Project
local IpUtils = {}
-- Simple bitwise AND for Lua 5.1 compatibility
local function bitwise_and(a, b)
local result = 0
local bit = 1
while a > 0 or b > 0 do
if (a % 2 == 1) and (b % 2 == 1) then
result = result + bit
end
a = math.floor(a / 2)
b = math.floor(b / 2)
bit = bit * 2
end
return result
end
-- Create subnet mask for given CIDR bits
local function create_mask(mask_bits)
if mask_bits >= 32 then
return 0xFFFFFFFF
elseif mask_bits <= 0 then
return 0
else
-- Create mask: 32-bit with 'mask_bits' ones from left
local mask = 0
for i = 0, mask_bits - 1 do
mask = mask + math.pow(2, 31 - i)
end
return mask
end
end
-- CIDR IP matching function (Lua 5.1 compatible)
function IpUtils.ip_matches_cidr(ip, cidr)
if not cidr:find("/") then
-- No subnet mask, direct comparison
return ip == cidr
end
local network, mask_bits = cidr:match("([^/]+)/(%d+)")
if not network or not mask_bits then
return false
end
mask_bits = tonumber(mask_bits)
-- Simple IPv4 CIDR matching
if ip:find("%.") and network:find("%.") then
-- Convert IPv4 to number
local function ip_to_num(ip_str)
local parts = {}
for part in ip_str:gmatch("(%d+)") do
table.insert(parts, tonumber(part))
end
if #parts == 4 then
return (parts[1] * 16777216) + (parts[2] * 65536) + (parts[3] * 256) + parts[4]
end
return 0
end
local ip_num = ip_to_num(ip)
local network_num = ip_to_num(network)
-- Create subnet mask
local mask = create_mask(mask_bits)
-- Apply mask to both IPs and compare
return bitwise_and(ip_num, mask) == bitwise_and(network_num, mask)
end
-- Fallback: if CIDR parsing fails, allow if IP matches network part
return ip == network or ip:find("^" .. network:gsub("%.", "%%."))
end
-- Check if IP is in allowed list
function IpUtils.is_ip_allowed(client_ip, allowed_ips)
if not allowed_ips or #allowed_ips == 0 then
return true -- No restrictions
end
for _, allowed_cidr in ipairs(allowed_ips) do
if IpUtils.ip_matches_cidr(client_ip, allowed_cidr) then
return true
end
end
return false
end
-- Extract client IP (considering proxies)
function IpUtils.get_client_ip(request)
-- Check for forwarded IP headers first
local forwarded_for = request.headers["x-forwarded-for"]
if forwarded_for then
-- Take first IP from comma-separated list
local first_ip = forwarded_for:match("([^,]+)")
if first_ip then
return first_ip:match("^%s*(.-)%s*$") -- trim whitespace
end
end
local real_ip = request.headers["x-real-ip"]
if real_ip then
return real_ip
end
-- Fallback to connection IP (would need socket info, defaulting to localhost for now)
return "127.0.0.1"
end
return IpUtils