]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/rules: add basic view capability
authorVladimír Čunát <vladimir.cunat@nic.cz>
Sun, 3 Jul 2022 13:15:40 +0000 (15:15 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Mon, 12 Jun 2023 08:32:28 +0000 (10:32 +0200)
Example:
  assert(require('ffi').C.kr_view_insert_action(
   '127.0.0.0/24', 'policy.DENY_MSG("message")'
) == 0)

daemon/lua/kres-gen-30.lua
daemon/lua/kres-gen-31.lua
daemon/lua/kres-gen-32.lua
daemon/lua/kres-gen.sh
lib/rules/api.c
lib/rules/api.h
modules/policy/policy.lua

index 968748918f3b596fe3d363a3dec78978557f945c..1e4a288025e8a44da115f0d810951a0b03e3c9d6 100644 (file)
@@ -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;
index c07888e9c8aab4120e4d7fb908a5bd2c967fe74d..810560dd2799e8e603e526d7f9eb2c79b67ce5b0 100644 (file)
@@ -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;
index d63fe6660a93768d14888e2931276895af28199d..84156d05fd13c43409f31c395da23dc27e143c03 100644 (file)
@@ -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;
index 3d6fcfaf10d8788389260ac941dfd7e4a2070e1d..b56a8a38c21d8fd9325942d0a306926f46bc3dc3 100755 (executable)
@@ -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
 
 
index 896eb95e94a6d0fb1b4eba8ed4d5d637e465eb23..b8f27b55c7d896b277989ed194b0834d223d2fd3 100644 (file)
@@ -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);
+}
index dd3f2bf6821079e6963aabcba4a0ca030b3cc9fb..e9d10277e71e21c5e7490d9a8900f7bcb5da7c59 100644 (file)
@@ -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);
+
index 64689801d1f845c5a78660ea8407716b714e81a3..1990837aa5a44632532e40aabfd6936adf588214 100644 (file)
@@ -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