minetest-verbana/lib_asn.lua

145 lines
4.5 KiB
Lua

verbana.lib_asn = {}
verbana.lib_asn.invalid_asn_description = 'Invalid ASN'
local lib_ip = verbana.lib_ip
local settings = verbana.settings
local load_file = verbana.util.load_file
-- map from ASN (integer) to description (string)
verbana.lib_asn.description = {}
local function refresh_asn_descriptions()
local contents = load_file(settings.asn_description_path)
if not contents then return end
local description = {}
for line in contents:gmatch('[^\r\n]+') do
local asn, desc = line:match('^%s*(%d+)%s+(.*)$')
if not asn or not desc then
verbana.log('warning', 'could not interpret description line "%s"', line)
else
asn = tonumber(asn)
description[asn] = desc
end
end
verbana.lib_asn.description = description
return true
end
-- array of values like {starting_ip (integer), ending_ip (integer), ASN (integer)}
-- ip ranges should be sorted and not overlap.
verbana.lib_asn.network = {}
local function refresh_asn_table()
-- format of source file: IP/NET ASN
-- source file is assumed to be sorted
-- source file may have overlapping networks corresponding to subleases;
-- extra logic is used to resolve these overlaps.
local contents = load_file(settings.asn_data_path)
if not contents then return end
local networks = {}
for line in contents:gmatch('[^\r\n]+') do
local net, asn = line:match('^%s*(%S*)%s+(%S*)%s*$')
if not asn or not net then
verbana.log('warning', 'could not interpret network line "%s"', line)
else
asn = tonumber(asn)
local start, end_ = lib_ip.netstr_to_bounds(net)
if #networks == 0 then
table.insert(networks, {start, end_, asn})
else
local prev_net = networks[#networks]
local prev_start = prev_net[1]
local prev_end = prev_net[2]
local prev_asn = prev_net[3]
if prev_start <= start and prev_end >= end_ and prev_asn == asn then
-- redundant data, skip
elseif start <= prev_end then
-- subnet delegated to someone else; split the prev network
table.remove(networks)
if prev_start ~= (start - 1) then
table.insert(networks, {prev_start, start - 1, prev_asn })
end
table.insert(networks, {start, end_, asn })
if (end_ + 1) ~= prev_end then
table.insert(networks, {end_ + 1, prev_end, prev_asn })
end
elseif (prev_end + 1) == start and prev_asn == asn then
-- adjacent networks belong to the same ASN; just extend the existing network
networks[#networks][2] = end_
else
-- default case
table.insert(networks, {start, end_, asn})
end
end
end
end
verbana.lib_asn.network = networks
return true
end
function verbana.lib_asn.refresh()
local start = os.clock()
if not refresh_asn_descriptions() then return false end
if not refresh_asn_table() then return false end
verbana.log('action', 'refreshed ASN tables in %s seconds', os.clock() - start)
return true
end
if not verbana.lib_asn.refresh() then
error('Verbana could not load ASN data. Please see README.md for instructions.')
end
local function find(ipint)
-- binary search
local t = verbana.lib_asn.network
local low = 1
local high = #t
while low <= high do
local mid = math.floor((low + high) / 2)
local element = t[mid]
local start = element[1]
local end_ = element[2]
if start <= ipint and ipint <= end_ then
return element[3]
elseif start > ipint then
high = mid - 1
else
low = mid + 1
end
end
-- not found, return nil
end
function verbana.lib_asn.lookup(ipstr)
local ipint
if type(ipstr) == 'number' then
ipint = ipstr
else
ipint = lib_ip.ipstr_to_ipint(ipstr)
end
local asn = find(ipint)
if asn then
return asn, (verbana.lib_asn.description[asn] or 'UNKNOWN')
else
return 0, 'Not part of a known ASN'
end
end
function verbana.lib_asn.get_description(asn)
if asn == 0 then
return 'Not part of a known ASN'
else
return verbana.lib_asn.description[asn] or verbana.lib_asn.invalid_asn_description
end
end