Commit 0ee2d1d7 authored by Marek Vavruša's avatar Marek Vavruša

modules/block: reworked for new FFI bindings

parent 28565f82
......@@ -401,7 +401,7 @@ int engine_start(struct engine *engine)
/* Clean up stack and restart GC */
lua_settop(engine->L, 0);
lua_gc(engine->L, LUA_GCCOLLECT, 0);
lua_gc(engine->L, LUA_GCSETSTEPMUL, 99);
lua_gc(engine->L, LUA_GCSETSTEPMUL, 50);
lua_gc(engine->L, LUA_GCSETPAUSE, 400);
lua_gc(engine->L, LUA_GCRESTART, 0);
return kr_ok();
......
......@@ -21,6 +21,8 @@ There are three action:
* ``DENY`` - return NXDOMAIN answer
* ``DROP`` - terminate query resolution, returns SERVFAIL to requestor
.. note:: The module (and ``kres``) treats domain names as wire, not textual representation. So each label in name is prefixed with its length, e.g. "example.com" equals to "\7example\3com".
Example configuration
^^^^^^^^^^^^^^^^^^^^^
......@@ -29,18 +31,18 @@ Example configuration
-- Load default block rules
modules = { 'block' }
-- Whitelist 'www[0-9].badboy.cz'
block:add(block.pattern(block.PASS, 'www[0-9].badboy.cz'))
block:add(block.pattern(block.PASS, '\4www[0-9]\6badboy\2cz'))
-- Block all names below badboy.cz
block:add(block.suffix(block.DENY, {'badboy.cz'}))
block:add(block.suffix(block.DENY, {'\6badboy\2cz'}))
-- Custom rule
block:add(function (pkt, qname)
if qname:find('%d.%d.%d.224.in-addr.arpa.') then
return block.DENY, '224.in-addr.arpa.'
block:add(function (req, query)
if query:qname():find('%d.%d.%d.224\7in-addr\4arpa') then
return block.DENY
end
end)
-- Disallow ANY queries
block:add(function (pkt, qname)
if pkt:qtype() == kres.rrtype.ANY then
block:add(function (req, query)
if query.type == kres.type.ANY then
return block.DROP
end
end)
......@@ -51,11 +53,10 @@ Properties
.. envvar:: block.PASS (number)
.. envvar:: block.DENY (number)
.. envvar:: block.DROP (number)
.. envvar:: block.private_zones (table of private zones)
.. function:: block:add(rule)
:param rule: added rule, i.e. ``block.pattern(block.DENY, '[0-9]+.cz')``
:param rule: added rule, i.e. ``block.pattern(block.DENY, '[0-9]+\2cz')``
:param pattern: regular expression
Policy to block queries based on the QNAME regex matching.
......@@ -81,8 +82,7 @@ Properties
:param common_suffix: common suffix of entries in suffix_table
Like suffix match, but you can also provide a common suffix of all matches for faster processing (nil otherwise).
.. tip:: If you want to match suffixes only, prefix the strings with `.`, e.g. `.127.in-addr.arpa.` instead of `127.in-addr.arpa`.
This function is faster for small suffix tables (in the order of "hundreds").
.. _`Aho-Corasick`: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm
.. _`@jgrahamc`: https://github.com/jgrahamc/aho-corasick-lua
......
local kres = require('kres')
local block = {
-- Policies
PASS = 1, DENY = 2, DROP = 3,
-- Special values
ANY = 0,
-- Private, local, broadcast, test and special zones
private_zones = {
-- RFC1918
'.10.in-addr.arpa.',
'.16.172.in-addr.arpa.',
'.17.172.in-addr.arpa.',
'.18.172.in-addr.arpa.',
'.19.172.in-addr.arpa.',
'.20.172.in-addr.arpa.',
'.21.172.in-addr.arpa.',
'.22.172.in-addr.arpa.',
'.23.172.in-addr.arpa.',
'.24.172.in-addr.arpa.',
'.25.172.in-addr.arpa.',
'.26.172.in-addr.arpa.',
'.27.172.in-addr.arpa.',
'.28.172.in-addr.arpa.',
'.29.172.in-addr.arpa.',
'.30.172.in-addr.arpa.',
'.31.172.in-addr.arpa.',
'.168.192.in-addr.arpa.',
-- RFC5735, RFC5737
'.0.in-addr.arpa.',
'.127.in-addr.arpa.',
'.254.169.in-addr.arpa.',
'.2.0.192.in-addr.arpa.',
'.100.51.198.in-addr.arpa.',
'.113.0.203.in-addr.arpa.',
'255.255.255.255.in-addr.arpa.',
-- IPv6 local, example
'0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
'.d.f.ip6.arpa.',
'.8.e.f.ip6.arpa.',
'.9.e.f.ip6.arpa.',
'.a.e.f.ip6.arpa.',
'.b.e.f.ip6.arpa.',
'.8.b.d.0.1.0.0.2.ip6.arpa',
}
}
-- @function Block requests which QNAME matches given zone list (i.e. suffix match)
function block.suffix(action, zone_list)
local AC = require('aho-corasick')
local tree = AC.build(zone_list)
return function(pkt, qname)
local match = AC.match(tree, qname, false)
return function(req, query)
local match = AC.match(tree, query:name(), false)
if match[1] ~= nil then
return action
end
......@@ -61,15 +23,15 @@ end
function block.suffix_common(action, suffix_list, common_suffix)
local common_len = string.len(common_suffix)
local suffix_count = #suffix_list
return function(pkt, qname)
return function(req, query)
-- Preliminary check
local qname = query:name()
if not string.find(qname, common_suffix, -common_len, true) then
return nil
end
-- String match
local zone = nil
for i = 1, suffix_count do
zone = suffix_list[i]
local zone = suffix_list[i]
if string.find(qname, zone, -string.len(zone), true) then
return action
end
......@@ -80,8 +42,8 @@ end
-- @function Block QNAME pattern
function block.pattern(action, pattern)
return function(pkt, qname)
if string.find(qname, pattern) then
return function(req, query)
if string.find(query:name(), pattern) then
return action
end
return nil
......@@ -89,9 +51,9 @@ function block.pattern(action, pattern)
end
-- @function Evaluate packet in given rules to determine block action
function block.evaluate(block, pkt, qname)
for i = 1, block.rules_count do
local action = block.rules[i](pkt, qname)
function block.evaluate(block, req, query)
for i = 1, #block.rules do
local action = block.rules[i](req, query)
if action ~= nil then
return action
end
......@@ -101,46 +63,79 @@ end
-- @function Block layer implementation
block.layer = {
produce = function(state, req, pkt)
-- Check only for first iteration of a query
if state == kres.DONE then
return state
end
local qry = kres.query_current(req)
if kres.query.flag(qry, kres.query.AWAIT_CUT) then
return state
end
local qname = kres.query.qname(qry)
local action = block:evaluate(pkt, qname)
begin = function(state, req)
req = kres.request_t(req)
local action = block:evaluate(req, req:current())
if action == block.DENY then
-- Answer full question
local qclass = kres.query.qclass(qry)
local qtype = kres.query.qtype(qry)
kres.query.flag(qry, kres.query.NO_MINIMIZE + kres.query.CACHED)
pkt:question(qname, qtype, qclass)
pkt:flag(kres.wire.QR)
-- Write authority information
pkt:rcode(kres.rcode.NXDOMAIN)
pkt:begin(kres.AUTHORITY)
pkt:add('block.', qclass, kres.type.SOA, 900,
local answer = req.answer
answer:rcode(kres.rcode.NXDOMAIN)
answer:begin(kres.section.AUTHORITY)
answer:put('\5block', 900, answer:qclass(), kres.type.SOA,
'\5block\0\0\0\0\0\0\0\0\14\16\0\0\3\132\0\9\58\128\0\0\3\132')
return kres.DONE
elseif action == block.DROP then
return kres.FAIL
else
return state
end
return state
end
}
-- @var Default rules
block.rules_count = 1
block.rules = { block.suffix_common(block.DENY, block.private_zones, 'arpa.') }
-- @function Add rule to block list
function block.add(block, rule)
block.rules_count = block.rules_count + 1
return table.insert(block.rules, rule)
end
-- @function Convert list of string names to domain names
function block.to_domains(names)
for i, v in ipairs(names) do
names[i] = v:gsub('([^.]*%.)', function (x)
return string.format('%s%s', string.char(x:len()-1), x:sub(1,-2))
end)
end
end
-- RFC1918 Private, local, broadcast, test and special zones
local private_zones = {
'10.in-addr.arpa.',
'16.172.in-addr.arpa.',
'17.172.in-addr.arpa.',
'18.172.in-addr.arpa.',
'19.172.in-addr.arpa.',
'20.172.in-addr.arpa.',
'21.172.in-addr.arpa.',
'22.172.in-addr.arpa.',
'23.172.in-addr.arpa.',
'24.172.in-addr.arpa.',
'25.172.in-addr.arpa.',
'26.172.in-addr.arpa.',
'27.172.in-addr.arpa.',
'28.172.in-addr.arpa.',
'29.172.in-addr.arpa.',
'30.172.in-addr.arpa.',
'31.172.in-addr.arpa.',
'168.192.in-addr.arpa.',
-- RFC5735, RFC5737
'0.in-addr.arpa.',
'127.in-addr.arpa.',
'254.169.in-addr.arpa.',
'2.0.192.in-addr.arpa.',
'100.51.198.in-addr.arpa.',
'113.0.203.in-addr.arpa.',
'255.255.255.255.in-addr.arpa.',
-- IPv6 local, example
'0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
'd.f.ip6.arpa.',
'8.e.f.ip6.arpa.',
'9.e.f.ip6.arpa.',
'a.e.f.ip6.arpa.',
'b.e.f.ip6.arpa.',
'8.b.d.0.1.0.0.2.ip6.arpa',
}
block.to_domains(private_zones)
-- @var Default rules
block.rules = { block.suffix_common(block.DENY, private_zones, '\4arpa') }
return block
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment