]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
/views/*/{dst_subnet,protocols}: add, both backend+config
authorVladimír Čunát <vladimir.cunat@nic.cz>
Sun, 3 Sep 2023 15:33:39 +0000 (17:33 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Thu, 5 Oct 2023 10:30:48 +0000 (12:30 +0200)
Examples:
 - tagging based on dst_subnet is useful for providing different
   filtering setting on different resolver addresses
 - tagging based on protocols is useful to signal used transport
   (change in DNS data that can be read by the final app)

(docs added in a later commit)

daemon/lua/kres-gen-30.lua
daemon/lua/kres-gen-31.lua
daemon/lua/kres-gen-32.lua
daemon/lua/kres-gen.sh
daemon/session2.h
lib/resolve.h
lib/rules/api.c
lib/rules/api.h
manager/knot_resolver_manager/datamodel/templates/macros/view_macros.lua.j2
manager/knot_resolver_manager/datamodel/templates/views.lua.j2
manager/knot_resolver_manager/datamodel/view_schema.py

index c10a6ae3b6339d3a80bd55307a13c71844ef96e0..60b023933cee54e8971abc34395f62f432a694c3 100644 (file)
@@ -348,6 +348,8 @@ struct kr_query_data_src {
        knot_db_val_t targets_ptr;
 };
 enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
+enum kr_proto {KR_PROTO_INTERNAL, KR_PROTO_UDP53, KR_PROTO_TCP53, KR_PROTO_DOT, KR_PROTO_DOH, KR_PROTO_DOQ, KR_PROTO_COUNT};
+typedef unsigned char kr_proto_set;
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
@@ -492,7 +494,7 @@ 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_rules_init(const char *, size_t);
-int kr_view_insert_action(const char *, const char *);
+int kr_view_insert_action(const char *, const char *, kr_proto_set, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
 int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
index 821c60688d1989f49e501f004f9640372469cac5..8bbfbf67f3f71de5b53816e3933759b39c7132bc 100644 (file)
@@ -348,6 +348,8 @@ struct kr_query_data_src {
        knot_db_val_t targets_ptr;
 };
 enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
+enum kr_proto {KR_PROTO_INTERNAL, KR_PROTO_UDP53, KR_PROTO_TCP53, KR_PROTO_DOT, KR_PROTO_DOH, KR_PROTO_DOQ, KR_PROTO_COUNT};
+typedef unsigned char kr_proto_set;
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
@@ -492,7 +494,7 @@ 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_rules_init(const char *, size_t);
-int kr_view_insert_action(const char *, const char *);
+int kr_view_insert_action(const char *, const char *, kr_proto_set, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
 int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
index 11afea269c711550e1e340a09586c8daade4aeb2..16b29d6d8976d5d0a6df4f1d08f856704e9bc2a3 100644 (file)
@@ -349,6 +349,8 @@ struct kr_query_data_src {
        knot_db_val_t targets_ptr;
 };
 enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
+enum kr_proto {KR_PROTO_INTERNAL, KR_PROTO_UDP53, KR_PROTO_TCP53, KR_PROTO_DOT, KR_PROTO_DOH, KR_PROTO_DOQ, KR_PROTO_COUNT};
+typedef unsigned char kr_proto_set;
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
@@ -493,7 +495,7 @@ 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_rules_init(const char *, size_t);
-int kr_view_insert_action(const char *, const char *);
+int kr_view_insert_action(const char *, const char *, kr_proto_set, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
 int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
index 609fe044e8f3942f73f9e4203ad9b38b1adf1847..dcc530aedaaa847e481aacc002639f354370977e 100755 (executable)
@@ -148,6 +148,8 @@ ${CDEFS} ${LIBKRES} types <<-EOF
        enum kr_log_group
        struct kr_query_data_src
        enum kr_rule_sub_t
+       enum kr_proto
+       kr_proto_set
 EOF
 
 ${CDEFS} ${KRESD} variables <<-EOF
index 2a25e9d11b7d59bd4bbe88a41a762e365bb928a5..426f9b1fbff110c8d5e68f618b28563126ea71f2 100644 (file)
@@ -243,6 +243,8 @@ const char *protolayer_protocol_name(enum protolayer_protocol p);
  *
  * For defining new groups, see the docs of `protolayer_grps[]` in
  * `daemon/session2.h`.
+ * 
+ * TODO: probably unify enum protolayer_grp with enum kr_proto.
  *
  * Parameters for XX are:
  *   1. Constant name (for e.g. PROTOLAYER_GRP_* enum value identifiers)
index 2ead6e2692967af891459256a3962638477501d7..5fc665416eff268dedfa5d65800445ba8ef4ae75 100644 (file)
@@ -186,7 +186,7 @@ int kr_resolver_init(module_array_t *modules, knot_mm_t *pool);
 KR_EXPORT
 void kr_resolver_deinit(void);
 
-/* Kept outside, because kres-gen.lua can't handle this depth
+/* Kept outside struct kr_request, because kres-gen.lua can't handle this depth
  * (and lines here were too long anyway). */
 struct kr_request_qsource_flags {
        bool tcp:1; /**< true if the request is not on UDP; only meaningful if (dst_addr). */
index a656eb7e907b62d87a1446e27908d7340c534d9d..cdedd2aec67619403d7523a6825f6c2db8b71c9a 100644 (file)
@@ -31,7 +31,7 @@ const uint32_t KR_RULE_TTL_DEFAULT = 300;
     - 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()
+       -> conditions + action-rule string; see kr_view_insert_action()
  */
 
 /*const*/ char RULESET_DEFAULT[] = "d";
@@ -874,8 +874,10 @@ bool subnet_is_prefix(uint8_t a, uint8_t b)
                memcpy(key.data, arr, sizeof(arr)); \
        } while (false)
 
-int kr_view_insert_action(const char *subnet, const char *action)
+int kr_view_insert_action(const char *subnet, const char *dst_subnet,
+                       kr_proto_set protos, const char *action)
 {
+       if (*dst_subnet == '\0') dst_subnet = NULL; // convenience for the API
        ENSURE_the_rules;
        // Parse the subnet string.
        union kr_sockaddr saddr;
@@ -901,15 +903,83 @@ int kr_view_insert_action(const char *subnet, const char *action)
                memcpy(key.data, RULESET_DEFAULT, rsp_len);
        }
 
-       // Insert.  We overwrite, as subnet is the only condition so far and that's in key.
-       knot_db_val_t val = {
-               .data = (void *)/*const-cast*/action,
-               .len = strlen(action),
-       };
-       int ret = ruledb_op(remove, &key, 1);  kr_assert(ret == 0 || ret == 1);
+       // We have the key; start constructing the value to insert.
+       const int dst_maxlen = 1 + (dst_subnet ? kr_family_len(saddr.ip.sa_family) : 0);
+       const int action_len = strlen(action);
+       uint8_t buf[sizeof(protos) + dst_maxlen + action_len];
+       uint8_t *data = buf;
+       int dlen = 0;
+
+       memcpy(data, &protos, sizeof(protos));
+       data += sizeof(protos);
+       dlen += sizeof(protos);
+
+       uint8_t dst_bitlen = 0;
+       if (dst_subnet) {
+               // For simplicity, we always write the whole address,
+               // even if some bytes at the end are useless (keep it iff dst_bitlen > 0).
+               int ret = kr_straddr_subnet(data + sizeof(dst_bitlen), dst_subnet);
+               if (ret < 0) {
+                       kr_log_error(RULES, "failed to parse destination subnet: %s\n",
+                                       dst_subnet);
+                       return kr_error(ret);
+               }
+               if (saddr.ip.sa_family != kr_straddr_family(dst_subnet)) {
+                       kr_log_error(RULES,
+                               "destination subnet mismatching IPv4 vs. IPv6: %s\n",
+                               dst_subnet);
+                       return kr_error(EINVAL);
+               }
+               dst_bitlen = ret;
+       }
+       memcpy(data, &dst_bitlen, sizeof(dst_bitlen));
+       if (dst_bitlen > 0) {
+               data += dst_maxlen; // address bytes already written above
+               dlen += dst_maxlen;
+       } else {
+               data += sizeof(dst_bitlen);
+               dlen += sizeof(dst_bitlen);
+       }
+
+       memcpy(data, action, action_len);
+       data += action_len;
+       dlen += action_len;
+
+       kr_require(data <= buf + dlen);
+       knot_db_val_t val = { .data = buf, .len = dlen };
        return ruledb_op(write, &key, &val, 1);
 }
 
+static enum kr_proto req_proto(const struct kr_request *req)
+{
+       if (!req->qsource.addr)
+               return KR_PROTO_INTERNAL;
+       const struct kr_request_qsource_flags fl = req->qsource.flags;
+       if (fl.http)
+               return KR_PROTO_DOH;
+       if (fl.tcp)
+               return fl.tls ? KR_PROTO_DOT : KR_PROTO_TCP53;
+       // UDP in some form
+       return fl.tls ? KR_PROTO_DOQ : KR_PROTO_UDP53;
+}
+static bool req_proto_matches(const struct kr_request *req, kr_proto_set proto_set)
+{
+       if (!proto_set) // empty set always matches
+               return true;
+       kr_proto_set mask = 1 << req_proto(req);
+       return mask & proto_set;
+}
+static void log_action(const struct kr_request *req, knot_db_val_t act)
+{
+       if (!kr_log_is_debug(RULES, req))
+               return;
+       // it's complex to get zero-terminated string for the action
+       char act_0t[act.len + 1];
+       memcpy(act_0t, act.data, act.len);
+       act_0t[act.len] = 0;
+       VERBOSE_MSG(req->rplan.initial, "=> view selected action: %s\n", act_0t);
+}
+
 int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected)
 {
        kr_require(the_rules);
@@ -983,18 +1053,37 @@ int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected)
                                }
                        }
                        // We certainly have a matching key (join of various sub-cases).
-                       // LATER: we'd iterate on this key's entries and find one
-                       //   that matches additional conditions (optional ones in future)
-                       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);
+                       // But multiple variants are possible, and conditions inside values.
+                       for (ret = ruledb_op(it_first, &key_leq, &val);
+                                       ret == 0;
+                                       ret = ruledb_op(it_next, &val)) {
+                               kr_proto_set protos;
+                               if (deserialize_fails_assert(&val, &protos))
+                                       continue;
+                               if (!req_proto_matches(req, protos))
+                                       continue;
+                               uint8_t dst_bitlen;
+                               if (deserialize_fails_assert(&val, &dst_bitlen))
+                                       continue;
+                               if (dst_bitlen) {
+                                       const int abytes = kr_inaddr_len(addr);
+                                       const char *dst_a = kr_inaddr(req->qsource.dst_addr);
+                                       if (kr_fails_assert(val.len >= abytes))
+                                               continue;
+                                       if (kr_bitcmp(val.data, dst_a, dst_bitlen) != 0)
+                                               continue;
+                                       val.data += abytes;
+                                       val.len  -= abytes;
+                               }
+                               // we passed everything; `val` contains just the action
+                               log_action(req, val);
+                               *selected = val;
+                               return kr_ok();
                        }
-                       *selected = val;
-                       return kr_ok();
+                       // Key matched but none of the condition variants;
+                       // we may still get a match with a wider subnet rule -> continue.
+                       // LATER(optim.): it's possible that something could be made
+                       //   somewhat faster in this various jumping around keys.
                }
        }
        return kr_error(ENOENT);
index 52715c5cf6ccb7b7ff5fef3e343e5d88b03d8316..d6bf97a100e216795033463b075308d6274fdb48 100644 (file)
@@ -16,6 +16,27 @@ typedef uint64_t kr_rule_tags_t;
 /// Tags "capacity", i.e. numbered from 0 to _CAP - 1.
 #define KR_RULE_TAGS_CAP (sizeof(kr_rule_tags_t) * 8)
 
+/** DNS protocol set - mutually exclusive options, contrary to kr_request_qsource_flags
+ *
+ * The XDP flag is not discerned here, as it could apply to any protocol.
+ *  (not right now, but libknot does support it for TCP, so that would complete everything)
+ *
+ * TODO: probably unify with enum protolayer_grp.
+ */
+enum kr_proto {
+       KR_PROTO_INTERNAL = 0, /// no protocol, e.g. useful to mark internal requests
+       KR_PROTO_UDP53,
+       KR_PROTO_TCP53,
+       KR_PROTO_DOT,
+       KR_PROTO_DOH,
+       KR_PROTO_DOQ, /// unused for now
+       KR_PROTO_COUNT,
+};
+/** Bitmap of enum kr_proto options. */
+typedef uint8_t kr_proto_set;
+static_assert(sizeof(kr_proto_set) * 8 >= KR_PROTO_COUNT, "bad combination of type sizes");
+
+
 /** Open the rule DB.
  *
  * You can call this to override the path or size (NULL/0 -> default).
@@ -149,16 +170,24 @@ int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
 /** Insert a view action into the default ruleset.
  *
  * \param subnet String specifying a subnet, e.g. "192.168.0.0/16".
+ * \param dst_subnet String specifying a subnet to be matched by the destination address. (or empty/NULL)
+ * \param protos Set of transport protocols. (or 0 to always match)
  * \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: some way to do multiple actions?  Will be useful e.g. with option-setting actions.
- *    On implementation side this would probably be multi-value LMDB, cf. local_data rules.
+ * TODO: improve? (return code, warning, ...)  Internal queries never get matched.
+ *
+ * The concept of chain actions isn't respected; at most one action is chosen.
+ * The winner needs to fulfill all conditions.  Closer subnet match is preferred,
+ * but otherwise the priority is unspecified (it is deterministic, though).
+ *
+ * There's no detection of action rules that clash in this way,
+ * even if all conditions match exactly.
+ * TODO we might consider some overwriting semantics,
+ *   but the additional conditions make that harder.
  */
 KR_EXPORT
-int kr_view_insert_action(const char *subnet, const char *action);
+int kr_view_insert_action(const char *subnet, const char *dst_subnet,
+                       kr_proto_set protos, const char *action);
 
 /** Add a tag by name into a tag-set variable.
  *
index efd032113a5cbb493c715912860d1d6de5f2aea3..b61cc3f03474f6a948721d8eec6908b57d32f284 100644 (file)
@@ -1,5 +1,13 @@
-{% macro view_insert_action(subnet, action) -%}
-assert(C.kr_view_insert_action('{{ subnet }}',{{ action }})==0)
+{%- macro get_proto_set(protocols) -%}
+0
+{%- for p in protocols or [] -%}
+ + 2^C.KR_PROTO_{{ p.upper() }}
+{%- endfor -%}
+{%- endmacro -%}
+
+{% macro view_insert_action(view, subnet, action) -%}
+assert(C.kr_view_insert_action('{{ subnet }}', '{{ view.dst_subnet or '' }}',
+       {{ get_proto_set(view.protocols) }}, {{ action }})==0)
 {%- endmacro %}
 
 {% macro view_flags(options) -%}
index 99c654c920495db70d51a6f2a53308aeb7793c7d..d4357cbbe1ab5ad2a01a34c7c4b2b5e47a716ec1 100644 (file)
@@ -7,14 +7,14 @@
 {% for subnet in view.subnets %}
 
 {% if view.tags -%}
-{{ view_insert_action(subnet, policy_tags_assign(view.tags)) }}
+{{ view_insert_action(view, subnet, policy_tags_assign(view.tags)) }}
 {% elif view.answer %}
-{{ view_insert_action(subnet, view_answer(view.answer)) }}
+{{ view_insert_action(view, subnet, view_answer(view.answer)) }}
 {%- endif %}
 
 {%- set flags = view_flags(view.options) -%}
 {% if flags -%}
-{{ view_insert_action(subnet, quotes(policy_flags(flags))) }}
+{{ view_insert_action(view, subnet, quotes(policy_flags(flags))) }}
 {%- endif %}
 
 {% endfor %}
index 74bf5a111e4c2d3c09f0592a3078afa755167bf8..98d4a1a0dd506aa065659d71cbdf1563d170fb4d 100644 (file)
@@ -24,13 +24,18 @@ class ViewSchema(ConfigSchema):
     Configuration parameters that allow you to create personalized policy rules and other.
 
     ---
-    subnets: Identifies the client based on his subnet.
+    subnets: Identifies the client based on his subnet.  Rule with more precise subnet takes priority.
+    dst_subnet: Destination subnet, as an additional condition.
+    protocols: Transport protocol, as an additional condition.
     tags: Tags to link with other policy rules.
     answer: Direct approach how to handle request from clients identified by the view.
     options: Configuration options for clients identified by the view.
     """
 
     subnets: List[IPNetwork]
+    dst_subnet: Optional[IPNetwork] = None  # could be a list as well, iterated in template
+    protocols: Optional[List[Literal["udp53", "tcp53", "dot", "doh", "doq"]]] = None
+
     tags: Optional[List[IDPattern]] = None
     answer: Optional[Literal["allow", "refused", "noanswer"]] = None
     options: ViewOptionsSchema = ViewOptionsSchema()