145 lines
4.5 KiB
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
|