summaryrefslogtreecommitdiffstats
path: root/lib/cookies
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
commitc1f743ab2e4a7046d5500875a47d1f62c8624603 (patch)
tree709946d52f5f3bbaeb38be9e3f1d56d11f058237 /lib/cookies
parentInitial commit. (diff)
downloadknot-resolver-c1f743ab2e4a7046d5500875a47d1f62c8624603.tar.xz
knot-resolver-c1f743ab2e4a7046d5500875a47d1f62c8624603.zip
Adding upstream version 5.7.1.upstream/5.7.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/cookies')
-rw-r--r--lib/cookies/alg_containers.c59
-rw-r--r--lib/cookies/alg_containers.h37
-rw-r--r--lib/cookies/alg_sha.c110
-rw-r--r--lib/cookies/alg_sha.h18
-rw-r--r--lib/cookies/control.h37
-rw-r--r--lib/cookies/helper.c268
-rw-r--r--lib/cookies/helper.h74
-rw-r--r--lib/cookies/lru_cache.c58
-rw-r--r--lib/cookies/lru_cache.h57
-rw-r--r--lib/cookies/nonce.c20
-rw-r--r--lib/cookies/nonce.h31
11 files changed, 769 insertions, 0 deletions
diff --git a/lib/cookies/alg_containers.c b/lib/cookies/alg_containers.c
new file mode 100644
index 0000000..1da0bda
--- /dev/null
+++ b/lib/cookies/alg_containers.c
@@ -0,0 +1,59 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <libknot/cookies/alg-fnv64.h>
+
+#include "lib/cookies/alg_containers.h"
+#include "lib/cookies/alg_sha.h"
+
+const struct knot_cc_alg *kr_cc_alg_get(int id)
+{
+ /*
+ * Client algorithm identifiers are used to index this array of
+ * pointers.
+ */
+ static const struct knot_cc_alg *const cc_algs[] = {
+ /* 0 */ &knot_cc_alg_fnv64,
+ /* 1 */ &knot_cc_alg_hmac_sha256_64
+ };
+
+ if (id >= 0 && id < 2) {
+ return cc_algs[id];
+ }
+
+ return NULL;
+}
+
+const knot_lookup_t kr_cc_alg_names[] = {
+ { 0, "FNV-64" },
+ { 1, "HMAC-SHA256-64" },
+ { -1, NULL }
+};
+
+const struct knot_sc_alg *kr_sc_alg_get(int id)
+{
+ /*
+ * Server algorithm identifiers are used to index this array of
+ * pointers.
+ */
+ static const struct knot_sc_alg *const sc_algs[] = {
+ /* 0 */ &knot_sc_alg_fnv64,
+ /* 1 */ &knot_sc_alg_hmac_sha256_64
+ };
+
+ if (id >= 0 && id < 2) {
+ return sc_algs[id];
+ }
+
+ return NULL;
+}
+
+const knot_lookup_t kr_sc_alg_names[] = {
+ { 0, "FNV-64" },
+ { 1, "HMAC-SHA256-64" },
+ { -1, NULL }
+};
diff --git a/lib/cookies/alg_containers.h b/lib/cookies/alg_containers.h
new file mode 100644
index 0000000..5764c28
--- /dev/null
+++ b/lib/cookies/alg_containers.h
@@ -0,0 +1,37 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/cookies/client.h>
+#include <libknot/cookies/server.h>
+#include <libknot/lookup.h>
+
+#include "lib/defines.h"
+
+/**
+ * @brief Returns pointer to client cookie algorithm.
+ *
+ * @param id algorithm identifier as defined by lookup table
+ * @return pointer to algorithm structure with given id or NULL on error
+ */
+KR_EXPORT
+const struct knot_cc_alg *kr_cc_alg_get(int id);
+
+/** Binds client algorithm identifiers onto names. */
+KR_EXPORT
+extern const knot_lookup_t kr_cc_alg_names[];
+
+/**
+ * @brief Returns pointer to server cookie algorithm.
+ *
+ * @param id algorithm identifier as defined by lookup table
+ * @return pointer to algorithm structure with given id or NULL on error
+ */
+KR_EXPORT
+const struct knot_sc_alg *kr_sc_alg_get(int id);
+
+/** Binds server algorithm identifiers onto names. */
+KR_EXPORT
+extern const knot_lookup_t kr_sc_alg_names[];
diff --git a/lib/cookies/alg_sha.c b/lib/cookies/alg_sha.c
new file mode 100644
index 0000000..34e79c3
--- /dev/null
+++ b/lib/cookies/alg_sha.c
@@ -0,0 +1,110 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <nettle/hmac.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <libknot/errcode.h>
+#include <libknot/rrtype/opt-cookie.h>
+
+#include "lib/cookies/alg_sha.h"
+#include "lib/utils.h"
+
+/**
+ * @brief Update hash value.
+ *
+ * @param ctx HMAC-SHA256 context to be updated.
+ * @param sa Socket address.
+ */
+static inline void update_hash(struct hmac_sha256_ctx *ctx,
+ const struct sockaddr *sa)
+{
+ if (kr_fails_assert(ctx && sa))
+ return;
+
+ int addr_len = kr_inaddr_len(sa);
+ const uint8_t *addr = (uint8_t *)kr_inaddr(sa);
+
+ if (addr && addr_len > 0) {
+ hmac_sha256_update(ctx, addr_len, addr);
+ }
+}
+
+/**
+ * @brief Compute client cookie using HMAC-SHA256-64.
+ * @note At least one of the arguments must be non-null.
+ * @param input input parameters
+ * @param cc_out buffer for computed client cookie
+ * @param cc_len buffer size
+ * @return Non-zero size of written data on success, 0 in case of a failure.
+ */
+static uint16_t cc_gen_hmac_sha256_64(const struct knot_cc_input *input,
+ uint8_t *cc_out, uint16_t cc_len)
+{
+ if (!knot_cc_input_is_valid(input) ||
+ !cc_out || cc_len < KNOT_OPT_COOKIE_CLNT) {
+ return 0;
+ }
+
+ struct hmac_sha256_ctx ctx;
+ hmac_sha256_set_key(&ctx, input->secret_len, input->secret_data);
+
+ if (input->clnt_sockaddr) {
+ update_hash(&ctx, input->clnt_sockaddr);
+ }
+
+ if (input->srvr_sockaddr) {
+ update_hash(&ctx, input->srvr_sockaddr);
+ }
+
+ /* KNOT_OPT_COOKIE_CLNT <= SHA256_DIGEST_SIZE */
+
+ hmac_sha256_digest(&ctx, KNOT_OPT_COOKIE_CLNT, cc_out);
+
+ return KNOT_OPT_COOKIE_CLNT;
+}
+
+#define SRVR_HMAC_SHA256_64_HASH_SIZE 8
+
+/**
+ * @brief Compute server cookie hash using HMAC-SHA256-64).
+ * @note Server cookie = nonce | time | HMAC-SHA256-64( server secret, client cookie | nonce| time | client IP )
+ * @param input data to compute cookie from
+ * @param hash_out hash output buffer
+ * @param hash_len buffer size
+ * @return Non-zero size of written data on success, 0 in case of a failure.
+ */
+static uint16_t sc_gen_hmac_sha256_64(const struct knot_sc_input *input,
+ uint8_t *hash_out, uint16_t hash_len)
+{
+ if (!knot_sc_input_is_valid(input) ||
+ !hash_out || hash_len < SRVR_HMAC_SHA256_64_HASH_SIZE) {
+ return 0;
+ }
+
+ struct hmac_sha256_ctx ctx;
+ hmac_sha256_set_key(&ctx, input->srvr_data->secret_len,
+ input->srvr_data->secret_data);
+
+ hmac_sha256_update(&ctx, input->cc_len, input->cc);
+
+ if (input->nonce && input->nonce_len) {
+ hmac_sha256_update(&ctx, input->nonce_len, input->nonce);
+ }
+
+ if (input->srvr_data->clnt_sockaddr) {
+ update_hash(&ctx, input->srvr_data->clnt_sockaddr);
+ }
+
+ /* SRVR_HMAC_SHA256_64_HASH_SIZE < SHA256_DIGEST_SIZE */
+
+ hmac_sha256_digest(&ctx, SRVR_HMAC_SHA256_64_HASH_SIZE, hash_out);
+
+ return SRVR_HMAC_SHA256_64_HASH_SIZE;
+}
+
+const struct knot_cc_alg knot_cc_alg_hmac_sha256_64 = { KNOT_OPT_COOKIE_CLNT, cc_gen_hmac_sha256_64 };
+
+const struct knot_sc_alg knot_sc_alg_hmac_sha256_64 = { SRVR_HMAC_SHA256_64_HASH_SIZE, sc_gen_hmac_sha256_64 };
diff --git a/lib/cookies/alg_sha.h b/lib/cookies/alg_sha.h
new file mode 100644
index 0000000..e97972a
--- /dev/null
+++ b/lib/cookies/alg_sha.h
@@ -0,0 +1,18 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/cookies/client.h>
+#include <libknot/cookies/server.h>
+
+#include "lib/defines.h"
+
+/* These structures are not meant to be part of public interface. */
+
+/** HMAC-SHA256-64 client cookie algorithm. */
+extern const struct knot_cc_alg knot_cc_alg_hmac_sha256_64;
+
+/** HMAC-SHA256-64 server cookie algorithm. */
+extern const struct knot_sc_alg knot_sc_alg_hmac_sha256_64;
diff --git a/lib/cookies/control.h b/lib/cookies/control.h
new file mode 100644
index 0000000..475b3fd
--- /dev/null
+++ b/lib/cookies/control.h
@@ -0,0 +1,37 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "lib/defines.h"
+
+/** Holds secret quantity. */
+struct kr_cookie_secret {
+ size_t size; /*!< Secret quantity size. */
+ uint8_t data[]; /*!< Secret quantity data. */
+};
+
+/** Holds settings that have direct influence on cookie values computation. */
+struct kr_cookie_comp {
+ struct kr_cookie_secret *secr; /*!< Secret data. */
+ int alg_id; /*!< Cookie algorithm identifier. */
+};
+
+/** Holds settings that control client/server cookie behaviour. */
+struct kr_cookie_settings {
+ bool enabled; /**< Enable/disables DNS cookies functionality. */
+
+ struct kr_cookie_comp current; /**< Current cookie settings. */
+ struct kr_cookie_comp recent; /**< Recent cookie settings. */
+};
+
+/** DNS cookies controlling structure. */
+struct kr_cookie_ctx {
+ struct kr_cookie_settings clnt; /**< Client settings. */
+ struct kr_cookie_settings srvr; /**< Server settings. */
+};
diff --git a/lib/cookies/helper.c b/lib/cookies/helper.c
new file mode 100644
index 0000000..4867817
--- /dev/null
+++ b/lib/cookies/helper.c
@@ -0,0 +1,268 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libknot/rrtype/opt.h>
+#include <libknot/rrtype/opt-cookie.h>
+
+#include "lib/cookies/helper.h"
+#include "lib/defines.h"
+
+/**
+ * @brief Check whether there is a cached cookie that matches the current
+ * client cookie.
+ */
+static const uint8_t *peek_and_check_cc(kr_cookie_lru_t *cache, const void *sa,
+ const uint8_t *cc, uint16_t cc_len)
+{
+ if (kr_fails_assert(cache && sa && cc && cc_len))
+ return NULL;
+
+ const uint8_t *cached_opt = kr_cookie_lru_get(cache, sa);
+ if (!cached_opt)
+ return NULL;
+
+ const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) cached_opt);
+
+ if (cc_len == KNOT_OPT_COOKIE_CLNT &&
+ 0 == memcmp(cc, cached_cc, cc_len)) {
+ return cached_opt;
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Put a client cookie into the RR Set.
+ */
+static int opt_rr_put_cookie(knot_rrset_t *opt_rr, uint8_t *data,
+ uint16_t data_len, knot_mm_t *mm)
+{
+ if (kr_fails_assert(opt_rr && data && data_len > 0))
+ return kr_error(EINVAL);
+
+ const uint8_t *cc = NULL, *sc = NULL;
+ uint16_t cc_len = 0, sc_len = 0;
+
+ int ret = knot_edns_opt_cookie_parse(data, data_len, &cc, &cc_len,
+ &sc, &sc_len);
+ if (ret != KNOT_EOK)
+ return kr_error(EINVAL);
+ if (kr_fails_assert(data_len == cc_len + sc_len))
+ return kr_error(EINVAL);
+
+ uint16_t cookies_size = data_len;
+ uint8_t *cookies_data = NULL;
+
+ ret = knot_edns_reserve_unique_option(opt_rr, KNOT_EDNS_OPTION_COOKIE,
+ cookies_size, &cookies_data, mm);
+ if (ret != KNOT_EOK)
+ return kr_error(EINVAL);
+ if (kr_fails_assert(cookies_data))
+ return kr_error(EINVAL);
+
+ cookies_size = knot_edns_opt_cookie_write(cc, cc_len, sc, sc_len,
+ cookies_data, cookies_size);
+ if (cookies_size == 0)
+ return kr_error(EINVAL);
+ if (kr_fails_assert(cookies_size == data_len))
+ return kr_error(EINVAL);
+
+ return kr_ok();
+}
+
+/**
+ * @brief Puts entire EDNS option into the RR Set.
+ */
+static int opt_rr_put_cookie_opt(knot_rrset_t *opt_rr, uint8_t *option, knot_mm_t *mm)
+{
+ if (kr_fails_assert(opt_rr && option))
+ return kr_error(EINVAL);
+
+ uint16_t opt_code = knot_edns_opt_get_code(option);
+ if (opt_code != KNOT_EDNS_OPTION_COOKIE)
+ return kr_error(EINVAL);
+
+ uint16_t opt_len = knot_edns_opt_get_length(option);
+ uint8_t *opt_data = knot_edns_opt_get_data(option);
+ if (!opt_data || opt_len == 0)
+ return kr_error(EINVAL);
+
+ return opt_rr_put_cookie(opt_rr, opt_data, opt_len, mm);
+}
+
+int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp,
+ kr_cookie_lru_t *cookie_cache,
+ const struct sockaddr *clnt_sa,
+ const struct sockaddr *srvr_sa,
+ struct kr_request *req)
+{
+ if (!clnt_comp || !req)
+ return kr_error(EINVAL);
+
+ if (!req->ctx->opt_rr)
+ return kr_ok();
+
+ if (!clnt_comp->secr || (clnt_comp->alg_id < 0) || !cookie_cache)
+ return kr_error(EINVAL);
+
+ /*
+ * Generate client cookie from client address, server address and
+ * secret quantity.
+ */
+ struct knot_cc_input input = {
+ .clnt_sockaddr = clnt_sa,
+ .srvr_sockaddr = srvr_sa,
+ .secret_data = clnt_comp->secr->data,
+ .secret_len = clnt_comp->secr->size
+ };
+ uint8_t cc[KNOT_OPT_COOKIE_CLNT];
+ uint16_t cc_len = KNOT_OPT_COOKIE_CLNT;
+ const struct knot_cc_alg *cc_alg = kr_cc_alg_get(clnt_comp->alg_id);
+ if (!cc_alg)
+ return kr_error(EINVAL);
+ if (kr_fails_assert(cc_alg->gen_func))
+ return kr_error(EINVAL);
+ cc_len = cc_alg->gen_func(&input, cc, cc_len);
+ if (cc_len != KNOT_OPT_COOKIE_CLNT)
+ return kr_error(EINVAL);
+
+ const uint8_t *cached_cookie = peek_and_check_cc(cookie_cache,
+ srvr_sa, cc, cc_len);
+
+ /* Add cookie option. */
+ int ret;
+ if (cached_cookie) {
+ ret = opt_rr_put_cookie_opt(req->ctx->opt_rr,
+ (uint8_t *)cached_cookie,
+ req->ctx->pool);
+ } else {
+ ret = opt_rr_put_cookie(req->ctx->opt_rr, cc, cc_len,
+ req->ctx->pool);
+ }
+
+ return ret;
+}
+
+int kr_answer_write_cookie(struct knot_sc_input *sc_input,
+ const struct kr_nonce_input *nonce,
+ const struct knot_sc_alg *alg, knot_pkt_t *pkt)
+{
+ if (!sc_input || !sc_input->cc || sc_input->cc_len == 0)
+ return kr_error(EINVAL);
+
+ if (!sc_input->srvr_data || !sc_input->srvr_data->clnt_sockaddr ||
+ !sc_input->srvr_data->secret_data ||
+ !sc_input->srvr_data->secret_len) {
+ return kr_error(EINVAL);
+ }
+
+ if (!nonce)
+ return kr_error(EINVAL);
+
+ if (!alg || !alg->hash_size || !alg->hash_func)
+ return kr_error(EINVAL);
+
+ if (!pkt || !pkt->opt_rr)
+ return kr_error(EINVAL);
+
+ uint16_t nonce_len = KR_NONCE_LEN;
+ uint16_t hash_len = alg->hash_size;
+
+ /*
+ * Space for cookie is reserved inside the EDNS OPT RR of
+ * the answer packet.
+ */
+ uint8_t *cookie = NULL;
+ uint16_t cookie_len = knot_edns_opt_cookie_data_len(sc_input->cc_len,
+ nonce_len + hash_len);
+ if (cookie_len == 0)
+ return kr_error(EINVAL);
+
+ int ret = knot_edns_reserve_unique_option(pkt->opt_rr,
+ KNOT_EDNS_OPTION_COOKIE,
+ cookie_len, &cookie,
+ &pkt->mm);
+ if (ret != KNOT_EOK)
+ return kr_error(ENOMEM);
+ if (kr_fails_assert(cookie))
+ return kr_error(EFAULT);
+
+ /*
+ * Function knot_edns_opt_cookie_data_len() returns the sum of its
+ * parameters or zero. Anyway, let's check again.
+ */
+ if (cookie_len < (sc_input->cc_len + nonce_len + hash_len))
+ return kr_error(EINVAL);
+
+ /* Copy client cookie data portion. */
+ memcpy(cookie, sc_input->cc, sc_input->cc_len);
+
+ if (nonce_len) {
+ /* Write nonce data portion. */
+ kr_nonce_write_wire(cookie + sc_input->cc_len, nonce_len,
+ nonce);
+ /* Adjust input for written nonce value. */
+ sc_input->nonce = cookie + sc_input->cc_len;
+ sc_input->nonce_len = nonce_len;
+ }
+
+ hash_len = alg->hash_func(sc_input,
+ cookie + sc_input->cc_len + nonce_len,
+ hash_len);
+ /* Zero nonce values. */
+ sc_input->nonce = NULL;
+ sc_input->nonce_len = 0;
+
+ return (hash_len != 0) ? kr_ok() : kr_error(EINVAL);
+}
+
+int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode)
+{
+ /*
+ * RFC6891 6.1.3 -- extended RCODE forms the upper 8 bits of whole
+ * 12-bit RCODE (together with the 4 bits of 'normal' RCODE).
+ *
+ * | 11 10 09 08 07 06 05 04 | 03 02 01 00 |
+ * | 12-bit whole RCODE |
+ * | 8-bit extended RCODE | 4-bit RCODE |
+ */
+
+ if (!pkt || !knot_pkt_has_edns(pkt))
+ return kr_error(EINVAL);
+
+ uint8_t rcode = whole_rcode & 0x0f;
+ uint8_t ext_rcode = whole_rcode >> 4;
+ knot_wire_set_rcode(pkt->wire, rcode);
+ knot_edns_set_ext_rcode(pkt->opt_rr, ext_rcode);
+
+ return kr_ok();
+}
+
+uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt)
+{
+ if (!pkt || knot_wire_get_qdcount(pkt->wire) > 0)
+ return false;
+
+ if (knot_wire_get_qr(pkt->wire) != 0 || !pkt->opt_rr)
+ return false;
+
+ return knot_edns_get_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE);
+}
+
+int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies)
+{
+ if (!cookie_opt || !cookies)
+ return kr_error(EINVAL);
+
+ const uint8_t *cookie_data = knot_edns_opt_get_data(cookie_opt);
+ uint16_t cookie_len = knot_edns_opt_get_length(cookie_opt);
+ if (!cookie_data || cookie_len == 0)
+ return kr_error(EINVAL);
+
+ int ret = knot_edns_opt_cookie_parse(cookie_data, cookie_len,
+ &cookies->cc, &cookies->cc_len,
+ &cookies->sc, &cookies->sc_len);
+
+ return (ret == KNOT_EOK) ? kr_ok() : kr_error(EINVAL);
+}
diff --git a/lib/cookies/helper.h b/lib/cookies/helper.h
new file mode 100644
index 0000000..dfde90e
--- /dev/null
+++ b/lib/cookies/helper.h
@@ -0,0 +1,74 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/cookies/alg_containers.h"
+#include "lib/cookies/control.h"
+#include "lib/cookies/lru_cache.h"
+#include "lib/cookies/nonce.h"
+#include "lib/defines.h"
+#include "lib/resolve.h"
+
+/**
+ * @brief Updates DNS cookie in the request EDNS options.
+ * @note This function must be called before the request packet is finalised.
+ * @param clnt_comp client cookie control structure
+ * @param cookie_cache cookie cache
+ * @param clnt_sa client socket address
+ * @param srvr_sa server socket address
+ * @param req name resolution request
+ * @return kr_ok() or error code
+ */
+KR_EXPORT
+int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp,
+ kr_cookie_lru_t *cookie_cache,
+ const struct sockaddr *clnt_sa,
+ const struct sockaddr *srvr_sa,
+ struct kr_request *req);
+
+/**
+ * @brief Inserts a cookie option into the OPT RR. It does not write any
+ * wire data.
+ * @note The content of @a sc_input is modified. Any pre-set nonce value is
+ * ignored. After retuning its nonce value will be null.
+ * @param sc_input data needed to compute server cookie, nonce is ignored
+ * @param nonce nonce value that is actually used
+ * @param alg hash algorithm
+ * @param pkt DNS response packet
+ */
+KR_EXPORT
+int kr_answer_write_cookie(struct knot_sc_input *sc_input,
+ const struct kr_nonce_input *nonce,
+ const struct knot_sc_alg *alg, knot_pkt_t *pkt);
+
+/**
+ * @brief Set RCODE and extended RCODE.
+ * @param pkt DNS packet
+ * @param whole_rcode RCODE value
+ * @return kr_ok() or error code
+ */
+KR_EXPORT
+int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode);
+
+/**
+ * @brief Check whether packet is a server cookie request according to
+ * RFC7873 5.4.
+ * @param pkt Packet to be examined.
+ * @return Pointer to entire cookie option if is a server cookie query, NULL on
+ * errors or if packet doesn't contain cookies or if QDCOUNT > 0.
+ */
+KR_EXPORT
+uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt);
+
+/**
+ * @brief Parse cookies from cookie option.
+ * @param cookie_opt Cookie option.
+ * @param cookies Cookie structure to be set.
+ * @return kr_ok() on success, error if cookies are malformed.
+ */
+KR_EXPORT
+int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies);
diff --git a/lib/cookies/lru_cache.c b/lib/cookies/lru_cache.c
new file mode 100644
index 0000000..245d1c3
--- /dev/null
+++ b/lib/cookies/lru_cache.c
@@ -0,0 +1,58 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libknot/rrtype/opt.h>
+#include <string.h>
+
+#include "lib/cookies/lru_cache.h"
+#include "lib/utils.h"
+
+const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache,
+ const struct sockaddr *sa)
+{
+ if (!cache || !sa) {
+ return NULL;
+ }
+
+ int addr_len = kr_inaddr_len(sa);
+ const char *addr = kr_inaddr(sa);
+ if (!addr || addr_len <= 0) {
+ return NULL;
+ }
+
+ struct cookie_opt_data *cached = lru_get_try(cache, addr, addr_len);
+ return cached ? cached->opt_data : NULL;
+}
+
+int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa,
+ uint8_t *opt)
+{
+ if (!cache || !sa) {
+ return kr_error(EINVAL);
+ }
+
+ if (!opt) {
+ return kr_ok();
+ }
+
+ int addr_len = kr_inaddr_len(sa);
+ const char *addr = kr_inaddr(sa);
+ if (!addr || addr_len <= 0) {
+ return kr_error(EINVAL);
+ }
+
+ uint16_t opt_size = KNOT_EDNS_OPTION_HDRLEN +
+ knot_edns_opt_get_length(opt);
+
+ if (opt_size > KR_COOKIE_OPT_MAX_LEN) {
+ return kr_error(EINVAL);
+ }
+
+ struct cookie_opt_data *cached = lru_get_new(cache, addr, addr_len, NULL);
+ if (cached) {
+ memcpy(cached->opt_data, opt, opt_size);
+ }
+
+ return kr_ok();
+}
diff --git a/lib/cookies/lru_cache.h b/lib/cookies/lru_cache.h
new file mode 100644
index 0000000..a0f6cab
--- /dev/null
+++ b/lib/cookies/lru_cache.h
@@ -0,0 +1,57 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <netinet/in.h>
+#include <stdint.h>
+
+#if ENABLE_COOKIES
+#include <libknot/rrtype/opt.h>
+#include <libknot/rrtype/opt-cookie.h>
+#else
+#define KNOT_OPT_COOKIE_CLNT 8
+#define KNOT_OPT_COOKIE_SRVR_MAX 32
+#endif /* ENABLE_COOKIES */
+
+#include "lib/defines.h"
+#include "lib/generic/lru.h"
+
+/** Maximal size of a cookie option. */
+#define KR_COOKIE_OPT_MAX_LEN (KNOT_EDNS_OPTION_HDRLEN + KNOT_OPT_COOKIE_CLNT + KNOT_OPT_COOKIE_SRVR_MAX)
+
+/**
+ * Cookie option entry.
+ */
+struct cookie_opt_data {
+ uint8_t opt_data[KR_COOKIE_OPT_MAX_LEN];
+};
+
+/**
+ * DNS cookies tracking.
+ */
+typedef lru_t(struct cookie_opt_data) kr_cookie_lru_t;
+
+/**
+ * @brief Obtain LRU cache entry.
+ *
+ * @param cache cookie LRU cache
+ * @param sa socket address serving as key
+ * @return pointer to cached option or NULL if not found or error occurred
+ */
+KR_EXPORT
+const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache,
+ const struct sockaddr *sa);
+
+/**
+ * @brief Stores cookie option into LRU cache.
+ *
+ * @param cache cookie LRU cache
+ * @param sa socket address serving as key
+ * @param opt cookie option to be stored
+ * @return kr_ok() or error code
+ */
+KR_EXPORT
+int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa,
+ uint8_t *opt);
diff --git a/lib/cookies/nonce.c b/lib/cookies/nonce.c
new file mode 100644
index 0000000..1b50d87
--- /dev/null
+++ b/lib/cookies/nonce.c
@@ -0,0 +1,20 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libknot/wire.h>
+#include "lib/cookies/nonce.h"
+
+uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len,
+ const struct kr_nonce_input *input)
+{
+ if (!buf || buf_len < KR_NONCE_LEN || !input) {
+ return 0;
+ }
+
+ knot_wire_write_u32(buf, input->rand);
+ knot_wire_write_u32(buf + sizeof(uint32_t), input->time);
+ buf_len = 2 * sizeof(uint32_t);
+
+ return buf_len;
+}
diff --git a/lib/cookies/nonce.h b/lib/cookies/nonce.h
new file mode 100644
index 0000000..6c2970f
--- /dev/null
+++ b/lib/cookies/nonce.h
@@ -0,0 +1,31 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "lib/defines.h"
+
+/* RFC7873 Appendix B.2 mentions an algorithm using two values before the
+ * actual server cookie hash. */
+
+/** Nonce value length. */
+#define KR_NONCE_LEN 8
+
+/** Input data to generate nonce from. */
+struct kr_nonce_input {
+ uint32_t rand; /**< some random value */
+ uint32_t time; /**< time stamp */
+};
+
+/**
+ * @brief Writes server cookie nonce value into given buffer.
+ *
+ * @param buf buffer to write nonce data in wire format into
+ * @param buf_len buffer size
+ * @param input data to generate wire data from
+ * @return non-zero size of written data on success, 0 on failure
+ */
+KR_EXPORT
+uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len,
+ const struct kr_nonce_input *input);