#!/opt/lua/bin/lua -- $Cambridge: hermes/conf/exim/sbin/bogons,v 1.8 2009/12/18 13:23:49 fanf2 Exp $ -- -- obtain and convert RIR allocations into a checker for bogus IP addresses -- -- See ftp://ftp.arin.net/pub/stats/arin/README local require = require local io = require "io" local lpeg = require "lpeg" local os = require "os" local re = require "re" local arg = arg local error = error local getmetatable = getmetatable local ipairs = ipairs local pairs = pairs local print = print local select = select local stdin = io.stdin local stdout = io.stdout local stderr = io.stderr local tonumber = tonumber local tostring = tostring local type = type local unpack = unpack function io.readfile(name) local f,e = io.open(name) if not f then error(e) end return f:read"*a" end -- pythonic! getmetatable("").__mod = function (string,arg) if type(arg) == "table" then return string:format(unpack(arg)) else return string:format(arg) end end local function make_parser() -- environment for non-terminals local env = {} local function p(pat) return re.compile(pat, env) end setfenv(1,env) CRLF = lpeg.S"\r\n" INL = 1 - CRLF comment = p[[ "#" INL* CRLF+ ]] EOL = p[[ CRLF+ comment* / !. ]] BEGIN = p[[ CRLF* comment* ]] name = p[[ {[a-z]+} ]] num = p[[ {[0-9]+} ]] / tonumber hex = p[[ {[0-9a-fA-F]+} ]] / function (n) return tonumber(n,16) end ipv4 = p[[ num "." num "." num "." num ]] / function (a,b,c,d) return a * 16777216 + b * 65536 + c * 256 + d end ipv6 = p[[ (hex ":")+ ":" ]] / function (a,b,c,d) return "%4x:%4x:%4x:%4x::" % { a or 0, b or 0, c or 0, d or 0 } end asn = p[[ num ("." num)? ]] / function (a,b) if b then return a * 65536 + b else return a end end date = p[[ {[0-9]^8} ]] -- YYYYMMDD zone = p[[ {[+-][0-9]^4} ]] kind = p[[ {"ipv4" / "ipv6" / "asn"} ]] version = p[[ ("2|" {:registry: name :} "|" {:serial: num :} "|" {:count: num :} "|" {:start_date: date :} "|" {:end_date: date :} "|" {:zone: zone :} EOL) -> {} ]] summary = p[[ {: name "|*|" kind "|*|" num "|summary" EOL :} ]] header = p[[ BEGIN version summary+ ]] % function (data, registry, kind, count) if type(data.count) == "number" then data.count = { all = data.count } end if data[kind] then error("duplicate %s summary line" % kind) end data.count[kind] = count data[kind] = {} return data end record = p[[ ( {:registry: name :} "|" {:cc: {[A-Z]^2} :} "|" ( {:kind:{"ipv4"}:} "|" {:start: ipv4 :} / {:kind:{"ipv6"}:} "|" {:start: ipv6 :} / {:kind:{"asn"}:} "|" {:start: asn :} ) "|" {:size: num :} "|" {:date: date :} "|" ("allocated"/"assigned") ("|" INL+ )? EOL ) -> {} ]] prefix = p[[ header record+ ]] % function (data, entry) local t = data[entry.kind] t[#t+1] = entry return data end all = p[[ prefix !. ]] exim_client = p[[ .. " H=" .. (" [" ipv4 "]" [: ]) ]] return env end local parse = make_parser() local function count(t) return "from %s found %d/%d ipv4 %d/%d ipv6 %d/%d asn %d/%d total records" % { t.registry, #t.ipv4, t.count.ipv4, #t.ipv6, t.count.ipv6, #t.asn, t.count.asn, #t.ipv4 + #t.ipv6 + #t.asn, t.count.all } end local function parser(s) local t = parse.all:match(s) if t then return t end t = parse.prefix:match(s) if t then error(count(t)) else error("parse error") end end local RIR = { afrinic = "ftp://ftp.ripe.net/pub/stats/afrinic/delegated-afrinic-latest", apnic = "ftp://ftp.ripe.net/pub/stats/apnic/delegated-apnic-latest", arin = "ftp://ftp.arin.net/pub/stats/arin/delegated-arin-latest", lacnic = "ftp://ftp.ripe.net/pub/stats/lacnic/delegated-lacnic-latest", ripencc = "ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-latest", nicbr = "ftp://ftp.registro.br/pub/stats/delegated-ipv6-nicbr-latest", -- Avoid broken/slow servers: -- afrinic = "ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest", -- apnic = "ftp://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest", -- lacnic = "ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest", } for rir,url in pairs(RIR) do if arg[1] ~= "-n" then os.execute("wget --timestamping --retr-symlinks %s" % url) end local file = url:match("/([^/]+)$") local t = parser(io.readfile(file)) t.url = url RIR[rir] = t print(count(t)) end RIR.ucam = { ipv4 = { { start = parse.ipv4:match"192.168.0.0", size = 65536 }, { start = parse.ipv4:match"172.16.0.0", size = 65536*15 }, }} local slash24 = {} local allocations = 0 local epsilon = 1/256 for rir,t in pairs(RIR) do local ipv4 = t.ipv4 for i = 1,#ipv4 do local e = ipv4[i] local start, size = e.start / 256, e.size / 256 start = start - start % 1 allocations = allocations + size for j = start, start + size - epsilon do slash24[j] = true end end end print("%d allocated /24 blocks" % allocations) local function fmtipv4(a) return "%d.%d.%d.%d" % { (a / 16777216) % 256, (a / 65536) % 256, (a / 256) % 256, (a / 1) % 256 } end local find_bogons = parse.exim_client / function (a) a = a - a % 256 if not slash24[a/256] then return fmtipv4(a) else return false end end for line in stdin:lines() do local bogon = find_bogons:match(line) if bogon then print("bogon", bogon, line) end end -- eof