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) ...@@ -401,7 +401,7 @@ int engine_start(struct engine *engine)
/* Clean up stack and restart GC */ /* Clean up stack and restart GC */
lua_settop(engine->L, 0); lua_settop(engine->L, 0);
lua_gc(engine->L, LUA_GCCOLLECT, 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_GCSETPAUSE, 400);
lua_gc(engine->L, LUA_GCRESTART, 0); lua_gc(engine->L, LUA_GCRESTART, 0);
return kr_ok(); return kr_ok();
......
...@@ -21,6 +21,8 @@ There are three action: ...@@ -21,6 +21,8 @@ There are three action:
* ``DENY`` - return NXDOMAIN answer * ``DENY`` - return NXDOMAIN answer
* ``DROP`` - terminate query resolution, returns SERVFAIL to requestor * ``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 Example configuration
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
...@@ -29,18 +31,18 @@ Example configuration ...@@ -29,18 +31,18 @@ Example configuration
-- Load default block rules -- Load default block rules
modules = { 'block' } modules = { 'block' }
-- Whitelist 'www[0-9].badboy.cz' -- 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 all names below badboy.cz
block:add(block.suffix(block.DENY, {'badboy.cz'})) block:add(block.suffix(block.DENY, {'\6badboy\2cz'}))
-- Custom rule -- Custom rule
block:add(function (pkt, qname) block:add(function (req, query)
if qname:find('%d.%d.%d.224.in-addr.arpa.') then if query:qname():find('%d.%d.%d.224\7in-addr\4arpa') then
return block.DENY, '224.in-addr.arpa.' return block.DENY
end end
end) end)
-- Disallow ANY queries -- Disallow ANY queries
block:add(function (pkt, qname) block:add(function (req, query)
if pkt:qtype() == kres.rrtype.ANY then if query.type == kres.type.ANY then
return block.DROP return block.DROP
end end
end) end)
...@@ -51,11 +53,10 @@ Properties ...@@ -51,11 +53,10 @@ Properties
.. envvar:: block.PASS (number) .. envvar:: block.PASS (number)
.. envvar:: block.DENY (number) .. envvar:: block.DENY (number)
.. envvar:: block.DROP (number) .. envvar:: block.DROP (number)
.. envvar:: block.private_zones (table of private zones)
.. function:: block:add(rule) .. 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 :param pattern: regular expression
Policy to block queries based on the QNAME regex matching. Policy to block queries based on the QNAME regex matching.
...@@ -81,8 +82,7 @@ Properties ...@@ -81,8 +82,7 @@ Properties
:param common_suffix: common suffix of entries in suffix_table :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). Like suffix match, but you can also provide a common suffix of all matches for faster processing (nil otherwise).
This function is faster for small suffix tables (in the order of "hundreds").
.. tip:: If you want to match suffixes only, prefix the strings with `.`, e.g. `.127.in-addr.arpa.` instead of `127.in-addr.arpa`.
.. _`Aho-Corasick`: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm .. _`Aho-Corasick`: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm
.. _`@jgrahamc`: https://github.com/jgrahamc/aho-corasick-lua .. _`@jgrahamc`: https://github.com/jgrahamc/aho-corasick-lua
......
local kres = require('kres')
local block = { local block = {
-- Policies -- Policies
PASS = 1, DENY = 2, DROP = 3, PASS = 1, DENY = 2, DROP = 3,
-- Special values -- Special values
ANY = 0, 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 requests which QNAME matches given zone list (i.e. suffix match)
function block.suffix(action, zone_list) function block.suffix(action, zone_list)
local AC = require('aho-corasick') local AC = require('aho-corasick')
local tree = AC.build(zone_list) local tree = AC.build(zone_list)
return function(pkt, qname) return function(req, query)
local match = AC.match(tree, qname, false) local match = AC.match(tree, query:name(), false)
if match[1] ~= nil then if match[1] ~= nil then
return action return action
end end
...@@ -61,15 +23,15 @@ end ...@@ -61,15 +23,15 @@ end
function block.suffix_common(action, suffix_list, common_suffix) function block.suffix_common(action, suffix_list, common_suffix)
local common_len = string.len(common_suffix) local common_len = string.len(common_suffix)
local suffix_count = #suffix_list local suffix_count = #suffix_list
return function(pkt, qname) return function(req, query)
-- Preliminary check -- Preliminary check
local qname = query:name()
if not string.find(qname, common_suffix, -common_len, true) then if not string.find(qname, common_suffix, -common_len, true) then
return nil return nil
end end
-- String match -- String match
local zone = nil
for i = 1, suffix_count do 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 if string.find(qname, zone, -string.len(zone), true) then
return action return action
end end
...@@ -80,8 +42,8 @@ end ...@@ -80,8 +42,8 @@ end
-- @function Block QNAME pattern -- @function Block QNAME pattern
function block.pattern(action, pattern) function block.pattern(action, pattern)
return function(pkt, qname) return function(req, query)
if string.find(qname, pattern) then if string.find(query:name(), pattern) then
return action return action
end end
return nil return nil
...@@ -89,9 +51,9 @@ function block.pattern(action, pattern) ...@@ -89,9 +51,9 @@ function block.pattern(action, pattern)
end end
-- @function Evaluate packet in given rules to determine block action -- @function Evaluate packet in given rules to determine block action
function block.evaluate(block, pkt, qname) function block.evaluate(block, req, query)
for i = 1, block.rules_count do for i = 1, #block.rules do
local action = block.rules[i](pkt, qname) local action = block.rules[i](req, query)
if action ~= nil then if action ~= nil then
return action return action
end end
...@@ -101,46 +63,79 @@ end ...@@ -101,46 +63,79 @@ end
-- @function Block layer implementation -- @function Block layer implementation
block.layer = { block.layer = {
produce = function(state, req, pkt) begin = function(state, req)
-- Check only for first iteration of a query req = kres.request_t(req)
if state == kres.DONE then local action = block:evaluate(req, req:current())
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)
if action == block.DENY then 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 -- Write authority information
pkt:rcode(kres.rcode.NXDOMAIN) local answer = req.answer
pkt:begin(kres.AUTHORITY) answer:rcode(kres.rcode.NXDOMAIN)
pkt:add('block.', qclass, kres.type.SOA, 900, 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') '\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 return kres.DONE
elseif action == block.DROP then elseif action == block.DROP then
return kres.FAIL return kres.FAIL
else
return state
end end
return state
end 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 Add rule to block list
function block.add(block, rule) function block.add(block, rule)
block.rules_count = block.rules_count + 1
return table.insert(block.rules, rule) return table.insert(block.rules, rule)
end 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 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