diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
commit | 830407e88f9d40d954356c3754f2647f91d5c06a (patch) | |
tree | d6a0ece6feea91f3c656166dbaa884ef8a29740e /lib/cookies | |
parent | Initial commit. (diff) | |
download | knot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.tar.xz knot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.zip |
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | lib/cookies/alg_containers.c | 59 | ||||
-rw-r--r-- | lib/cookies/alg_containers.h | 37 | ||||
-rw-r--r-- | lib/cookies/alg_sha.c | 110 | ||||
-rw-r--r-- | lib/cookies/alg_sha.h | 18 | ||||
-rw-r--r-- | lib/cookies/control.h | 37 | ||||
-rw-r--r-- | lib/cookies/helper.c | 268 | ||||
-rw-r--r-- | lib/cookies/helper.h | 74 | ||||
-rw-r--r-- | lib/cookies/lru_cache.c | 58 | ||||
-rw-r--r-- | lib/cookies/lru_cache.h | 57 | ||||
-rw-r--r-- | lib/cookies/nonce.c | 20 | ||||
-rw-r--r-- | lib/cookies/nonce.h | 31 |
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); |