local cqueues = require('cqueues')
+-- Load dependent modules
+if not view then modules.load('view') end
+if not policy then modules.load('policy') end
+
+-- Actions
+local actions = {
+ pass = 1, deny = 2, drop = 3, tc = 4, forward = policy.FORWARD,
+}
+
+-- Filter rules per column
+local filters = {
+ -- Filter on QNAME (either pattern or suffix match)
+ qname = function (g)
+ local op, val = g(), todname(g())
+ if op == '~' then return policy.pattern(true, val)
+ elseif op == '=' then return policy.suffix(true, {val})
+ else error(string.format('invalid operator "%s" on qname', op)) end
+ end,
+ -- Filter on source address
+ src = function (g)
+ local op = g()
+ if op ~= '=' then error('source address supports only "=" operator') end
+ return view.rule(true, g())
+ end
+}
+
+local function parse_filter(tok, g)
+ local filter = filters[tok]
+ if not filter then error(string.format('invalid filter "%s"', tok)) end
+ return filter(g)
+end
+
+local function parse_rule(g)
+ local f = parse_filter(g(), g)
+ -- Compose filter functions on conjunctions
+ -- or terminate filter chain and return
+ local tok = g()
+ while tok do
+ if tok == 'AND' then
+ local fnext = parse_filter(g(), g)
+ f = function (req, qry) return f(req, qry) and fnext(req, qry) end
+ elseif tok == 'OR' then
+ local fnext = parse_filter(g(), g)
+ f = function (req, qry) return f(req, qry) or fnext(req, qry) end
+ else
+ break
+ end
+ tok = g()
+ print('next token is', tok)
+ end
+ return tok, f
+end
+
+local function parse_query(g)
+ local ok, action, filter = pcall(parse_rule, g)
+ if not ok then return nil, action end
+ if not actions[action] then return nil, string.format('invalid action "%s"', action) end
+ -- Parse and interpret action
+ action = actions[action]
+ if type(action) == 'function' then
+ action = action(g())
+ end
+ return action, filter
+end
+
+-- Compile a rule described by query language
+-- The query language is modelled by iptables/nftables
+-- conj = AND | OR
+-- op = IS | NOT | LIKE | IN
+-- filter = <key> <op> <expr>
+-- rule = <filter> | <filter> <conj> <rule>
+-- action = PASS | DENY | DROP | TC | FORWARD
+-- query = <rule> <action>
+local function compile(query)
+ local g = string.gmatch(query, '%S+')
+ return parse_query(g)
+end
+
-- Module declaration
local M = {
+ rules = {}
}
-- @function Public-facing API
http.snippets['/daf'] = {'Application Firewall', [[
<p>Hello world!</p>
]]}
+ M.rule('qname = *.example.com AND src = 127.0.0.1/8 deny')
+ -- M.rule('answer ~ (%w+).facebook.com AND src = 127.0.0.1/8 forward 8.8.8.8')
+end
+
+-- @function Add rule
+function M.rule(rule)
+ local action, filter = compile(rule)
+ if not action then error(filter) end
+ table.insert(M.rules, {rule, action, filter})
+ print(action, filter, rule)
end
return M
\ No newline at end of file
local subnet_cd = ffi.new('char[16]')
local family = C.kr_straddr_family(subnet)
local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
- table.insert(view.subnet, {family, subnet_cd, bitlen, policy})
+ local t = {family, subnet_cd, bitlen, policy}
+ table.insert(view.subnet, t)
+ return t
end
-- @function Match IP against given subnet
-- @function Find view for given request
local function evaluate(view, req)
- local answer = req.answer
local client_key = req.qsource.key
local match_cb = (client_key ~= nil) and view.key[client_key:owner()] or nil
-- Search subnets otherwise
return match_cb
end
+-- @function Return view policy rule
+function view.rule(action, subnet)
+ local subnet_cd = ffi.new('char[16]')
+ local family = C.kr_straddr_family(subnet)
+ local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
+ return function(req, _)
+ local src_addr = req.qsource.addr
+ if src_addr ~= nil and match_subnet(family, subnet_cd, bitlen, src_addr) then
+ return action
+ end
+ end
+end
+
-- @function Module layers
view.layer = {
begin = function(state, req)