1
0
Fork 0
knot-resolver/lib/cookies/helper.c
Daniel Baumann fbc604e215
Adding upstream version 5.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:56:17 +02:00

268 lines
7.8 KiB
C

/* 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);
}