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;
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);
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;
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);
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;
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);
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
*
* 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)
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). */
- 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";
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;
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);
}
}
// 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);
/// 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).
/** 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.
*
-{% 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) -%}
{% 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 %}
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()