268 lines
7.8 KiB
C
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);
|
|
}
|