summaryrefslogtreecommitdiffstats
path: root/lib/rplan.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rplan.c')
-rw-r--r--lib/rplan.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/lib/rplan.c b/lib/rplan.c
new file mode 100644
index 0000000..6ed8e4e
--- /dev/null
+++ b/lib/rplan.c
@@ -0,0 +1,308 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <libknot/descriptor.h>
+#include <libknot/errcode.h>
+
+#include "lib/rplan.h"
+#include "lib/resolve.h"
+#include "lib/cache/api.h"
+#include "lib/defines.h"
+#include "lib/layer.h"
+
+#define VERBOSE_MSG(qry, ...) QRVERBOSE(qry, "plan", __VA_ARGS__)
+#define QUERY_PROVIDES(q, name, cls, type) \
+ ((q)->sclass == (cls) && (q)->stype == type && knot_dname_is_equal((q)->sname, name))
+
+inline static unsigned char chars_or(const unsigned char a, const unsigned char b)
+{
+ return a | b;
+}
+
+/** Bits set to 1 in variable b will be set to zero in variable a. */
+inline static unsigned char chars_mask(const unsigned char a, const unsigned char b)
+{
+ return a & ~b;
+}
+
+/** Apply mod(a, b) to every byte a, b from fl1, fl2 and return result in fl1. */
+inline static void kr_qflags_mod(struct kr_qflags *fl1, struct kr_qflags fl2,
+ unsigned char mod(const unsigned char a, const unsigned char b))
+{
+ if (!fl1) abort();
+ union {
+ struct kr_qflags flags;
+ /* C99 section 6.5.3.4: sizeof(char) == 1 */
+ unsigned char chars[sizeof(struct kr_qflags)];
+ } tmp1, tmp2;
+ /* The compiler should be able to optimize all this into simple ORs. */
+ tmp1.flags = *fl1;
+ tmp2.flags = fl2;
+ for (size_t i = 0; i < sizeof(struct kr_qflags); ++i) {
+ tmp1.chars[i] = mod(tmp1.chars[i], tmp2.chars[i]);
+ }
+ *fl1 = tmp1.flags;
+}
+
+/**
+ * Set bits from variable fl2 in variable fl1.
+ * Bits which are not set in fl2 are not modified in fl1.
+ *
+ * @param[in,out] fl1
+ * @param[in] fl2
+ */
+void kr_qflags_set(struct kr_qflags *fl1, struct kr_qflags fl2)
+{
+ kr_qflags_mod(fl1, fl2, chars_or);
+}
+
+/**
+ * Clear bits from variable fl2 in variable fl1.
+ * Bits which are not set in fl2 are not modified in fl1.
+ *
+ * @param[in,out] fl1
+ * @param[in] fl2
+ */
+void kr_qflags_clear(struct kr_qflags *fl1, struct kr_qflags fl2)
+{
+ kr_qflags_mod(fl1, fl2, chars_mask);
+}
+
+static struct kr_query *query_create(knot_mm_t *pool, const knot_dname_t *name, uint32_t uid)
+{
+ struct kr_query *qry = mm_alloc(pool, sizeof(struct kr_query));
+ if (qry == NULL) {
+ return NULL;
+ }
+
+ memset(qry, 0, sizeof(struct kr_query));
+ if (name != NULL) {
+ qry->sname = knot_dname_copy(name, pool);
+ if (qry->sname == NULL) {
+ mm_free(pool, qry);
+ return NULL;
+ }
+ }
+
+ knot_dname_to_lower(qry->sname);
+ qry->uid = uid;
+ return qry;
+}
+
+static void query_free(knot_mm_t *pool, struct kr_query *qry)
+{
+ kr_zonecut_deinit(&qry->zone_cut);
+ mm_free(pool, qry->sname);
+ mm_free(pool, qry);
+}
+
+int kr_rplan_init(struct kr_rplan *rplan, struct kr_request *request, knot_mm_t *pool)
+{
+ if (rplan == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(rplan, 0, sizeof(struct kr_rplan));
+
+ rplan->pool = pool;
+ rplan->request = request;
+ array_init(rplan->pending);
+ array_init(rplan->resolved);
+ rplan->next_uid = 0;
+ return KNOT_EOK;
+}
+
+void kr_rplan_deinit(struct kr_rplan *rplan)
+{
+ if (rplan == NULL) {
+ return;
+ }
+
+ for (size_t i = 0; i < rplan->pending.len; ++i) {
+ query_free(rplan->pool, rplan->pending.at[i]);
+ }
+ for (size_t i = 0; i < rplan->resolved.len; ++i) {
+ query_free(rplan->pool, rplan->resolved.at[i]);
+ }
+ array_clear_mm(rplan->pending, mm_free, rplan->pool);
+ array_clear_mm(rplan->resolved, mm_free, rplan->pool);
+}
+
+bool kr_rplan_empty(struct kr_rplan *rplan)
+{
+ if (rplan == NULL) {
+ return true;
+ }
+
+ return rplan->pending.len == 0;
+}
+
+static struct kr_query *kr_rplan_push_query(struct kr_rplan *rplan,
+ struct kr_query *parent,
+ const knot_dname_t *name)
+{
+ if (rplan == NULL) {
+ return NULL;
+ }
+
+ /* Make sure there's enough space */
+ int ret = array_reserve_mm(rplan->pending, rplan->pending.len + 1, kr_memreserve, rplan->pool);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ struct kr_query *qry = query_create(rplan->pool, name, rplan->next_uid);
+ if (qry == NULL) {
+ return NULL;
+ }
+ rplan->next_uid += 1;
+ /* Class and type must be set outside this function. */
+ qry->flags = rplan->request->options;
+ qry->parent = parent;
+ qry->request = rplan->request;
+ qry->ns.ctx = rplan->request->ctx;
+ qry->ns.addr[0].ip.sa_family = AF_UNSPEC;
+ gettimeofday(&qry->timestamp, NULL);
+ qry->timestamp_mono = kr_now();
+ qry->creation_time_mono = parent ? parent->creation_time_mono : qry->timestamp_mono;
+ kr_zonecut_init(&qry->zone_cut, (const uint8_t *)"", rplan->pool);
+ qry->reorder = qry->flags.REORDER_RR ? kr_rand_bytes(sizeof(qry->reorder)) : 0;
+
+ /* When forwarding, keep the nameserver addresses. */
+ if (parent && parent->flags.FORWARD && qry->flags.FORWARD) {
+ ret = kr_nsrep_copy_set(&qry->ns, &parent->ns);
+ if (ret) {
+ query_free(rplan->pool, qry);
+ return NULL;
+ }
+ }
+
+ array_push(rplan->pending, qry);
+
+ return qry;
+}
+
+struct kr_query *kr_rplan_push_empty(struct kr_rplan *rplan, struct kr_query *parent)
+{
+ if (rplan == NULL) {
+ return NULL;
+ }
+
+ struct kr_query *qry = kr_rplan_push_query(rplan, parent, NULL);
+ if (qry == NULL) {
+ return NULL;
+ }
+
+ WITH_VERBOSE(qry) {
+ VERBOSE_MSG(qry, "plan '%s' type '%s' uid [%05u.%02u]\n", "", "",
+ qry->request ? qry->request->uid : 0, qry->uid);
+ }
+ return qry;
+}
+
+struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent,
+ const knot_dname_t *name, uint16_t cls, uint16_t type)
+{
+ if (rplan == NULL || name == NULL) {
+ return NULL;
+ }
+
+ struct kr_query *qry = kr_rplan_push_query(rplan, parent, name);
+ if (qry == NULL) {
+ return NULL;
+ }
+
+ qry->sclass = cls;
+ qry->stype = type;
+
+ WITH_VERBOSE(qry) {
+ KR_DNAME_GET_STR(name_str, name);
+ KR_RRTYPE_GET_STR(type_str, type);
+ VERBOSE_MSG(parent, "plan '%s' type '%s' uid [%05u.%02u]\n",
+ name_str, type_str,
+ qry->request ? qry->request->uid : 0, qry->uid);
+ }
+ return qry;
+}
+
+int kr_rplan_pop(struct kr_rplan *rplan, struct kr_query *qry)
+{
+ if (rplan == NULL || qry == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Make sure there's enough space */
+ int ret = array_reserve_mm(rplan->resolved, rplan->resolved.len + 1, kr_memreserve, rplan->pool);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Find the query, it will likely be on top */
+ for (size_t i = rplan->pending.len; i > 0; i--) {
+ if (rplan->pending.at[i - 1] == qry) {
+ array_del(rplan->pending, i - 1);
+ array_push(rplan->resolved, qry);
+ break;
+ }
+ }
+ return KNOT_EOK;
+}
+
+bool kr_rplan_satisfies(struct kr_query *closure, const knot_dname_t *name, uint16_t cls, uint16_t type)
+{
+ while (name && closure) {
+ if (QUERY_PROVIDES(closure, name, cls, type)) {
+ return true;
+ }
+ closure = closure->parent;
+ }
+ return false;
+}
+
+struct kr_query *kr_rplan_resolved(struct kr_rplan *rplan)
+{
+ if (rplan->resolved.len == 0) {
+ return NULL;
+ }
+ return array_tail(rplan->resolved);
+}
+
+struct kr_query *kr_rplan_last(struct kr_rplan *rplan)
+{
+ if (!kr_rplan_empty(rplan)) {
+ return array_tail(rplan->pending);
+ }
+
+ return kr_rplan_resolved(rplan);
+}
+
+struct kr_query *kr_rplan_find_resolved(struct kr_rplan *rplan, struct kr_query *parent,
+ const knot_dname_t *name, uint16_t cls, uint16_t type)
+{
+ struct kr_query *ret = NULL;
+ for (int i = 0; i < rplan->resolved.len; ++i) {
+ struct kr_query *q = rplan->resolved.at[i];
+ if (q->stype == type && q->sclass == cls &&
+ (parent == NULL || q->parent == parent) &&
+ knot_dname_is_equal(q->sname, name)) {
+ ret = q;
+ break;
+ }
+ }
+ return ret;
+}
+
+#undef VERBOSE_MSG