Skip to content
Snippets Groups Projects
Verified Commit 15013db5 authored by Vladimír Čunát's avatar Vladimír Čunát
Browse files

lib/rules: add basic view capability

Example:
  assert(require('ffi').C.kr_view_insert_action(
  		'127.0.0.0/24', 'policy.DENY_MSG("message")'
	) == 0)
parent 18da5a58
Branches
Tags
1 merge request!1407new policy engine
......@@ -30,6 +30,10 @@ typedef struct {
uint32_t size;
knot_rdata_t *rdata;
} knot_rdataset_t;
typedef struct knot_db_val {
void *data;
size_t len;
} knot_db_val_t;
typedef struct knot_mm {
void *ctx, *alloc, *free;
......@@ -461,6 +465,8 @@ int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
int kr_cache_commit(struct kr_cache *);
uint32_t packet_ttl(const knot_pkt_t *);
int kr_view_insert_action(const char *, const char *);
int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
typedef struct {
int sock_type;
_Bool tls;
......
......@@ -30,6 +30,10 @@ typedef struct {
uint32_t size;
knot_rdata_t *rdata;
} knot_rdataset_t;
typedef struct knot_db_val {
void *data;
size_t len;
} knot_db_val_t;
typedef struct knot_mm {
void *ctx, *alloc, *free;
......@@ -461,6 +465,8 @@ int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
int kr_cache_commit(struct kr_cache *);
uint32_t packet_ttl(const knot_pkt_t *);
int kr_view_insert_action(const char *, const char *);
int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
typedef struct {
int sock_type;
_Bool tls;
......
......@@ -30,6 +30,10 @@ typedef struct {
uint32_t size;
knot_rdata_t *rdata;
} knot_rdataset_t;
typedef struct knot_db_val {
void *data;
size_t len;
} knot_db_val_t;
typedef struct knot_mm {
void *ctx, *alloc, *free;
......@@ -472,6 +476,8 @@ int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
int kr_cache_commit(struct kr_cache *);
uint32_t packet_ttl(const knot_pkt_t *);
int kr_view_insert_action(const char *, const char *);
int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
typedef struct {
int sock_type;
_Bool tls;
......
......@@ -69,12 +69,13 @@ struct kr_cdb_api {};
struct lru {};
"
${CDEFS} ${LIBKRES} types <<-EOF
${CDEFS} libknot types <<-EOF
knot_section_t
knot_rrinfo_t
knot_dname_t
knot_rdata_t
knot_rdataset_t
knot_db_val_t
EOF
# The generator doesn't work well with typedefs of functions.
......@@ -283,6 +284,9 @@ ${CDEFS} ${LIBKRES} functions <<-EOF
kr_cache_commit
# FIXME: perhaps rename this exported symbol
packet_ttl
# New policy
kr_view_insert_action
kr_view_select_action
EOF
......
......@@ -34,6 +34,8 @@ struct kr_rules *the_rules = NULL;
-> exact-match rule (for the given name)
- KEY_ZONELIKE_A + dname_lf (no '\0' at end)
-> zone-like apex (on the given name)
- KEY_VIEW_SRC4 or KEY_VIEW_SRC6 + subnet_encode()
-> action-rule string; see kr_view_insert_action()
*/
#define KEY_RULESET_MAXLEN 16 /**< max. len of ruleset ID + 1(for kind) */
......@@ -42,6 +44,9 @@ static /*const*/ char RULESET_DEFAULT[] = "d";
static const uint8_t KEY_EXACT_MATCH[1] = "e";
static const uint8_t KEY_ZONELIKE_A [1] = "a";
static const uint8_t KEY_VIEW_SRC4[1] = "4";
static const uint8_t KEY_VIEW_SRC6[1] = "6";
/** The first byte of zone-like apex value is its type. */
typedef uint8_t val_zla_type_t;
enum {
......@@ -620,3 +625,183 @@ int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags)
return insert_trivial_zone(VAL_ZLAT_REDIRECT, apex, tags);
}
/** Encode a subnet into a (longer) string.
*
* The point is to have different encodings for different subnets,
* with using just byte-length strings (e.g. for ::/1 vs. ::/2).
* And we need to preserve order: FIXME description
* - natural partial order on subnets, one included in another
* - partial order on strings, one being a prefix of another
* - implies lexicographical order on the encoded strings
*
* Consequently, given a set of subnets, the t
*/
static int subnet_encode(const struct sockaddr *addr, int sub_len, uint8_t buf[32])
{
const int len = kr_inaddr_len(addr);
if (kr_fails_assert(len > 0))
return kr_error(len);
if (kr_fails_assert(sub_len >= 0 && sub_len <= 8 * len))
return kr_error(EINVAL);
const uint8_t *a = (const uint8_t *)/*sign*/kr_inaddr(addr);
// Algo: interleave bits of the address. Bit pairs:
// - 00 -> beyond the subnet's prefix
// - 10 -> zero bit within the subnet's prefix
// - 11 -> one bit within the subnet's prefix
// Multiplying one uint8_t by 01010101 (in binary) will do interleaving.
int i;
// Let's hope that compiler optimizes this into something reasonable.
for (i = 0; sub_len > 0; ++i, sub_len -= 8) {
uint16_t x = a[i] * 85; // interleave by zero bits
uint8_t sub_mask = 255 >> (8 - MIN(sub_len, 8));
uint16_t r = x | (sub_mask * 85 * 2);
buf[2*i] = r / 256;
buf[2*i + 1] = r % 256;
}
return i * 2;
}
// Is `a` subnet-prefix of `b`? (a byte format of subnet_encode())
bool subnet_is_prefix(uint8_t a, uint8_t b)
{
while (true) {
if (a >> 6 == 0)
return true;
if (a >> 6 != b >> 6) {
kr_assert(b >> 6 != 0);
return false;
}
a = (a << 2) & 0xff;
b = (b << 2) & 0xff;
}
}
#define KEY_PREPEND(key, arr) do { \
key.data -= sizeof(arr); \
key.len += sizeof(arr); \
memcpy(key.data, arr, sizeof(arr)); \
} while (false)
int kr_view_insert_action(const char *subnet, const char *action)
{
// Parse the subnet string.
union kr_sockaddr saddr;
saddr.ip.sa_family = kr_straddr_family(subnet);
int bitlen = kr_straddr_subnet((char *)/*const-cast*/kr_inaddr(&saddr.ip), subnet);
if (bitlen < 0) return kr_error(bitlen);
// Init the addr-based part of key.
uint8_t key_data[KEY_MAXLEN];
knot_db_val_t key;
key.data = &key_data[KEY_RULESET_MAXLEN];
key.len = subnet_encode(&saddr.ip, bitlen, key.data);
switch (saddr.ip.sa_family) {
case AF_INET: KEY_PREPEND(key, KEY_VIEW_SRC4); break;
case AF_INET6: KEY_PREPEND(key, KEY_VIEW_SRC6); break;
default: kr_assert(false); return kr_error(EINVAL);
}
{ // Write ruleset-specific prefix of the key.
const size_t rsp_len = strlen(RULESET_DEFAULT);
key.data -= rsp_len;
memcpy(key.data, RULESET_DEFAULT, rsp_len);
}
// Insert & commit.
knot_db_val_t val = {
.data = (void *)/*const-cast*/action,
.len = strlen(action),
};
int ret = ruledb_op(write, &key, &val, 1);
return ret < 0 ? ret : ruledb_op(commit);
}
int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected)
{
const struct sockaddr * const addr = req->qsource.addr;
if (!addr) return kr_error(ENOENT); // internal request; LATER: act somehow?
// Init the addr-based part of key; it's pretty static.
uint8_t key_data[KEY_MAXLEN];
knot_db_val_t key;
key.data = &key_data[KEY_RULESET_MAXLEN];
key.len = subnet_encode(addr, kr_inaddr_len(addr) * 8, key.data);
switch (kr_inaddr_family(addr)) {
case AF_INET: KEY_PREPEND(key, KEY_VIEW_SRC4); break;
case AF_INET6: KEY_PREPEND(key, KEY_VIEW_SRC6); break;
default: kr_assert(false); return kr_error(EINVAL);
}
int ret;
// Init code for managing the ruleset part of the key.
// LATER(optim.): we might cache the ruleset list a bit
uint8_t * const key_data_ruleset_end = key.data;
knot_db_val_t rulesets = { NULL, 0 };
{
uint8_t key_rs[] = "\0rulesets";
knot_db_val_t key_rsk = { .data = key_rs, .len = sizeof(key_rs) };
ret = ruledb_op(read, &key_rsk, &rulesets, 1);
}
if (ret != 0) return ret; // including ENOENT: no rulesets -> no rule used
const char *rulesets_str = rulesets.data;
// Iterate over all rulesets.
while (rulesets.len > 0) {
{ // Write ruleset-specific prefix of the key.
const size_t rsp_len = strnlen(rulesets_str, rulesets.len);
kr_require(rsp_len <= KEY_RULESET_MAXLEN - 1);
key.data = key_data_ruleset_end - rsp_len;
memcpy(key.data, rulesets_str, rsp_len);
rulesets_str += rsp_len + 1;
rulesets.len -= rsp_len + 1;
}
static_assert(sizeof(KEY_VIEW_SRC4) == sizeof(KEY_VIEW_SRC6),
"bad combination of constants");
const size_t addr_start_i = key_data_ruleset_end + sizeof(KEY_VIEW_SRC4)
- (const uint8_t *)key.data;
knot_db_val_t key_leq = {
.data = key.data,
.len = key.len + (key_data_ruleset_end - (uint8_t *)key.data),
};
knot_db_val_t val;
ret = ruledb_op(read_leq, &key_leq, &val);
for (; true; ret = ruledb_op(read_less, &key_leq, &val)) {
if (ret == -ENOENT) break;
if (ret < 0) return kr_error(ret);
if (ret > 0) { // found a previous key
ssize_t i = key_common_prefix(key, key_leq);
if (i < addr_start_i) // no suitable key can exist in DB
break;
if (i != key_leq.len) {
if (kr_fails_assert(i < key.len && i < key_leq.len))
break;
if (!subnet_is_prefix(((uint8_t *)key_leq.data)[i],
((uint8_t *)key.data)[i])) {
// the key doesn't match
// We can shorten the key to potentially
// speed up by skipping over whole subtrees.
key_leq.len = i + 1;
continue;
}
}
}
// We certainly have a matching key (join of various sub-cases).
if (kr_log_is_debug(RULES, NULL)) {
// it's complex to get zero-terminated string for the action
char act_0t[val.len + 1];
memcpy(act_0t, val.data, val.len);
act_0t[val.len] = 0;
VERBOSE_MSG(req->rplan.initial, "=> view selected action: %s\n",
act_0t);
}
*selected = val;
return kr_ok();
}
}
return kr_error(ENOENT);
}
......@@ -5,7 +5,9 @@
#include "lib/defines.h"
struct kr_query;
struct kr_request;
struct knot_pkt;
#include <libknot/db/db.h>
typedef uint64_t kr_rule_tags_t;
#define KR_RULE_TAGS_ALL ((kr_rule_tags_t)0)
......@@ -24,8 +26,17 @@ void kr_rules_deinit(void);
*/
int kr_rule_local_data_answer(struct kr_query *qry, struct knot_pkt *pkt);
/** Select the view action to perform.
*
* \param selected The action string from kr_view_insert_action()
* \return 0 or negative error code, in particular kr_error(ENOENT)
*/
KR_EXPORT
int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected);
/* API to modify the rule DB.
/* APIs to modify the rule DB.
*
* FIXME:
* - what about transactions in this API?
......@@ -60,3 +71,16 @@ int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags);
KR_EXPORT
int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags);
/** Insert a view action into the default ruleset.
*
* \param subnet String specifying a subnet, e.g. "192.168.0.0/16".
* \param action Currently a string to execute, like in old policies, e.g. `policy.REFUSE`
*
* The concept of chain actions isn't respected; the most prioritized rule wins.
* If exactly the same subnet is specified repeatedly, that rule gets overwritten silently.
* TODO: improve? (return code, warning, ...)
* TODO: a well-usable action that assigns a tag-set
*/
KR_EXPORT
int kr_view_insert_action(const char *subnet, const char *action);
......@@ -834,6 +834,8 @@ end
policy.rules = {}
policy.postrules = {}
local view_action_buf = ffi.new('knot_db_val_t[1]')
-- Top-down policy list walk until we hit a match
-- the caller is responsible for reordering policy list
-- from most specific to least specific.
......@@ -843,6 +845,12 @@ policy.layer = {
begin = function(state, req)
-- Don't act on "finished" cases.
if bit.band(state, bit.bor(kres.FAIL, kres.DONE)) ~= 0 then return state end
if ffi.C.kr_view_select_action(req, view_action_buf) == 0 then
local act_str = ffi.string(view_action_buf[0].data, view_action_buf[0].len)
return loadstring('return '..act_str)()(state, req)
end
local qry = req:initial() -- same as :current() but more descriptive
return policy.evaluate(policy.rules, req, qry, state)
or state
......
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