Commit b5ced57a authored by Mark Karpilovskij's avatar Mark Karpilovskij Committed by Daniel Salzman

libknot/edns: added caching of pointers to known options in OPT RR, refactoring

parent 0c14e4d0
/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2018 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
......@@ -140,13 +140,8 @@ static knotd_state_t cookies_process(knotd_state_t state, knot_pkt_t *pkt,
return state;
}
// Check if OPT RR is present.
if (qdata->query->opt_rr == NULL) {
return state;
}
// Check if the cookie option is present.
uint8_t *cookie_opt = knot_edns_get_option(qdata->query->opt_rr,
uint8_t *cookie_opt = knot_pkt_edns_option(qdata->query,
KNOT_EDNS_OPTION_COOKIE);
if (cookie_opt == NULL) {
return state;
......
......@@ -351,8 +351,16 @@ static const ctr_desc_t ctr_descs[] = {
static void incr_edns_option(knotd_mod_t *mod, const knot_pkt_t *pkt, unsigned ctr_name)
{
if (!knot_pkt_has_edns(pkt)) {
return;
}
knot_rdata_t *rdata = knot_rdataset_at(&pkt->opt_rr->rrs, 0);
wire_ctx_t wire = wire_ctx_init(rdata->data, rdata->len);
if (rdata == NULL || rdata->len == 0) {
return;
}
wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
while (wire_ctx_available(&wire) > 0) {
uint16_t opt_code = wire_ctx_read_u16(&wire);
uint16_t opt_len = wire_ctx_read_u16(&wire);
......@@ -499,10 +507,10 @@ static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
// Count EDNS occurrences.
if (stats->edns) {
if (qdata->query->opt_rr != NULL) {
if (knot_pkt_has_edns(qdata->query)) {
knotd_mod_stats_incr(mod, CTR_EDNS, EDNS_REQ, 1);
}
if (pkt->opt_rr != NULL && state != KNOTD_STATE_NOOP) {
if (knot_pkt_has_edns(pkt) && state != KNOTD_STATE_NOOP) {
knotd_mod_stats_incr(mod, CTR_EDNS, EDNS_RESP, 1);
}
}
......@@ -512,16 +520,16 @@ static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
if (state != KNOTD_STATE_NOOP && knot_wire_get_tc(pkt->wire)) {
knotd_mod_stats_incr(mod, CTR_FLAG, FLAG_TC, 1);
}
if (pkt->opt_rr != NULL && knot_edns_do(pkt->opt_rr)) {
if (knot_pkt_has_dnssec(pkt)) {
knotd_mod_stats_incr(mod, CTR_FLAG, FLAG_DO, 1);
}
}
// Count EDNS options.
if (stats->req_eopt && !knot_rrset_empty(qdata->query->opt_rr)) {
if (stats->req_eopt) {
incr_edns_option(mod, qdata->query, CTR_REQ_EOPT);
}
if (stats->resp_eopt && !knot_rrset_empty(pkt->opt_rr)) {
if (stats->resp_eopt) {
incr_edns_option(mod, pkt, CTR_RESP_EOPT);
}
......
......@@ -266,7 +266,7 @@ static int answer_edns_init(const knot_pkt_t *query, knot_pkt_t *resp,
}
/* Append NSID if requested and available. */
if (knot_edns_has_option(query->opt_rr, KNOT_EDNS_OPTION_NSID)) {
if (knot_pkt_edns_option(query, KNOT_EDNS_OPTION_NSID) != NULL) {
conf_val_t *nsid = &conf()->cache.srv_nsid;
size_t nsid_len;
const uint8_t *nsid_data = conf_bin(nsid, &nsid_len);
......
......@@ -63,17 +63,11 @@ static void pkt_free_data(knot_pkt_t *pkt)
knot_rrset_clear(&pkt->rr[i], &pkt->mm);
}
}
/* Reset RR count. */
pkt->rrset_count = 0;
/* Reset special types. */
pkt->opt_rr = NULL;
pkt->tsig_rr = NULL;
/* Reset TSIG wire reference. */
pkt->tsig_wire.pos = NULL;
pkt->tsig_wire.len = 0;
/* Free EDNS option positions. */
mm_free(&pkt->mm, pkt->edns_opts);
pkt->edns_opts = 0;
}
/*! \brief Allocate new wireformat of given length. */
......@@ -317,6 +311,14 @@ static void payload_clear(knot_pkt_t *pkt)
/* Reset sections. */
sections_reset(pkt);
/* Reset special types. */
pkt->opt_rr = NULL;
pkt->tsig_rr = NULL;
/* Reset TSIG wire reference. */
pkt->tsig_wire.pos = NULL;
pkt->tsig_wire.len = 0;
}
_public_
......@@ -391,7 +393,7 @@ void knot_pkt_free(knot_pkt_t *pkt)
mm_free(&pkt->mm, pkt->rr);
mm_free(&pkt->mm, pkt->rr_info);
// free the space for wireformat
/* Free the space for wireformat. */
if (pkt->flags & KNOT_PF_FREE) {
mm_free(&pkt->mm, pkt->wire);
}
......@@ -503,7 +505,7 @@ int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr,
/* Check for double insertion. */
if ((flags & KNOT_PF_CHECKDUP) && pkt_contains(pkt, rr)) {
return KNOT_EOK; /*! \todo return rather a number of added RRs */
return KNOT_EOK;
}
knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count];
......@@ -617,27 +619,18 @@ int knot_pkt_parse_question(knot_pkt_t *pkt)
return KNOT_EOK;
}
/* \note Private for check_rr_constraints(). */
#define CHECK_AR_CONSTRAINTS(pkt, rr, var, check_func) \
if ((pkt)->current != KNOT_ADDITIONAL) { \
return KNOT_EMALF; \
} else if ((pkt)->var != NULL) { \
return KNOT_EMALF; \
} else if (!check_func(rr)) { \
return KNOT_EMALF; \
}
/*! \brief Check constraints (position, uniqueness, validity) for special types
* (TSIG, OPT).
*/
static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_size,
unsigned flags)
{
/* Check RR constraints. */
switch (rr->type) {
case KNOT_RRTYPE_TSIG:
CHECK_AR_CONSTRAINTS(pkt, rr, tsig_rr, knot_tsig_rdata_is_ok);
pkt->tsig_rr = rr;
if (pkt->current != KNOT_ADDITIONAL || pkt->tsig_rr != NULL ||
!knot_tsig_rdata_is_ok(rr)) {
return KNOT_EMALF;
}
/* Strip TSIG RR from wireformat and decrease ARCOUNT. */
if (!(flags & KNOT_PF_KEEPWIRE)) {
......@@ -647,9 +640,15 @@ static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_siz
pkt->tsig_wire.len = rr_size;
knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1);
}
pkt->tsig_rr = rr;
break;
case KNOT_RRTYPE_OPT:
CHECK_AR_CONSTRAINTS(pkt, rr, opt_rr, knot_edns_check_record);
if (pkt->current != KNOT_ADDITIONAL || pkt->opt_rr != NULL ||
knot_edns_get_options(rr, &pkt->edns_opts, &pkt->mm) != KNOT_EOK) {
return KNOT_EMALF;
}
pkt->opt_rr = rr;
break;
default:
......@@ -659,8 +658,6 @@ static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_siz
return KNOT_EOK;
}
#undef CHECK_AR_CONSTRAINTS
_public_
int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags)
{
......@@ -703,20 +700,16 @@ int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags)
return check_rr_constraints(pkt, rr, rr_size, flags);
}
_public_
int knot_pkt_parse_section(knot_pkt_t *pkt, unsigned flags)
static int parse_section(knot_pkt_t *pkt, unsigned flags)
{
if (pkt == NULL) {
return KNOT_EINVAL;
}
assert(pkt);
int ret = KNOT_EOK;
uint16_t rr_parsed = 0;
uint16_t rr_count = pkt_rr_wirecount(pkt, pkt->current);
/* Parse all RRs belonging to the section. */
for (rr_parsed = 0; rr_parsed < rr_count; ++rr_parsed) {
ret = knot_pkt_parse_rr(pkt, flags);
int ret = knot_pkt_parse_rr(pkt, flags);
if (ret != KNOT_EOK) {
return ret;
}
......@@ -754,7 +747,7 @@ int knot_pkt_parse_payload(knot_pkt_t *pkt, unsigned flags)
if (ret != KNOT_EOK) {
return ret;
}
ret = knot_pkt_parse_section(pkt, flags);
ret = parse_section(pkt, flags);
if (ret != KNOT_EOK) {
return ret;
}
......
......@@ -66,7 +66,6 @@ typedef struct {
* \brief Structure representing a DNS packet.
*/
struct knot_pkt {
uint8_t *wire; /*!< Wire format of the packet. */
size_t size; /*!< Current wire size of the packet. */
size_t max_size; /*!< Maximum allowed size of the packet. */
......@@ -79,7 +78,10 @@ struct knot_pkt {
knot_rrset_t *opt_rr; /*!< OPT RR included in the packet. */
knot_rrset_t *tsig_rr; /*!< TSIG RR stored in the packet. */
/* TSIG RR position in the wire (if parsed from wire). */
/*! EDNS option positions in the wire (if parsed from wire). */
knot_edns_options_t *edns_opts;
/*! TSIG RR position in the wire (if parsed from wire). */
struct {
uint8_t *pos;
size_t len;
......@@ -304,17 +306,6 @@ int knot_pkt_parse_question(knot_pkt_t *pkt);
*/
int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags);
/*!
* \brief Parse current packet section.
*
* \note For KNOT_PF_KEEPWIRE see note for \fn knot_pkt_parse_rr
*
* \param pkt
* \param flags
* \return KNOT_EOK, KNOT_EFEWDATA if not enough data or various errors
*/
int knot_pkt_parse_section(knot_pkt_t *pkt, unsigned flags);
/*!
* \brief Parse whole packet payload.
*
......@@ -354,7 +345,8 @@ const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt);
*/
static inline bool knot_pkt_has_edns(const knot_pkt_t *pkt)
{
return pkt != NULL && pkt->opt_rr != NULL;
assert(pkt);
return pkt->opt_rr != NULL;
}
/*!
......@@ -362,7 +354,8 @@ static inline bool knot_pkt_has_edns(const knot_pkt_t *pkt)
*/
static inline bool knot_pkt_has_tsig(const knot_pkt_t *pkt)
{
return pkt && pkt->tsig_rr;
assert(pkt);
return pkt->tsig_rr != NULL;
}
/*!
......@@ -370,16 +363,21 @@ static inline bool knot_pkt_has_tsig(const knot_pkt_t *pkt)
*/
static inline bool knot_pkt_has_dnssec(const knot_pkt_t *pkt)
{
assert(pkt);
return knot_pkt_has_edns(pkt) && knot_edns_do(pkt->opt_rr);
}
/*!
* \brief Checks if there is an NSID OPTION in the packet's OPT RR.
* \brief Get specific EDNS option from a parsed packet.
*/
static inline bool knot_pkt_has_nsid(const knot_pkt_t *pkt)
static inline uint8_t *knot_pkt_edns_option(const knot_pkt_t *pkt, uint16_t code)
{
return knot_pkt_has_edns(pkt)
&& knot_edns_has_option(pkt->opt_rr, KNOT_EDNS_OPTION_NSID);
assert(pkt);
if (pkt->edns_opts != NULL && code <= KNOT_EDNS_MAX_OPTION_CODE) {
return pkt->edns_opts->ptr[code];
} else {
return NULL;
}
}
/*! @} */
......@@ -26,7 +26,7 @@
#include "libknot/descriptor.h"
#include "libknot/rrtype/opt.h"
#include "libknot/packet/pkt.h"
#include "libknot/wire.h"
#include "contrib/mempattern.h"
#include "contrib/wire_ctx.h"
/*! \brief Some implementation-related constants. */
......@@ -129,72 +129,6 @@ void knot_edns_set_version(knot_rrset_t *opt_rr, uint8_t version)
set_value_to_ttl(opt_rr, EDNS_OFFSET_VERSION, version);
}
/*!
* \brief Skips an option from the supplied \a wire data.
*
* \param wire Wire data containing sequence of OPT RDATA.
* \param[out] code Code of the option that is at hand.
* \param[out] full_len Length of the entire skipped option with its header.
*
* \return Pointer to the first byte of the entire skipped option,
* NULL when none or incomplete option data left.
*/
static uint8_t *skip_option(wire_ctx_t *wire, uint16_t *code, uint16_t *full_len)
{
assert(wire && code && full_len);
uint8_t *position = NULL;
if (wire_ctx_available(wire) > 0) {
position = wire->position;
uint16_t opt_code = wire_ctx_read_u16(wire);
if (wire->error != KNOT_EOK) {
return NULL;
}
uint16_t opt_len = wire_ctx_read_u16(wire);
wire_ctx_skip(wire, opt_len);
/*
* Return position only when the entire option fits
* in the RDATA.
*/
if (wire->error == KNOT_EOK) {
*code = opt_code;
*full_len = KNOT_EDNS_OPTION_HDRLEN + opt_len;
return position;
}
}
return NULL;
}
/*!
* \brief Find OPTION with the given code in the OPT RDATA.
*
* \note It is ensured that the full option, as declared in option length,
* is encompassed in the RDATA when found.
*
* \param rdata RDATA to search in.
* \param opt_code Code of the OPTION to find.
*
* \return Pointer to the first byte of the first option that matches
* \a opt_code, NULL if no option found or error occurred.
*/
static uint8_t *find_option(knot_rdata_t *rdata, uint16_t opt_code)
{
wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
uint8_t *position = NULL;
uint16_t code, full_len;
while ((position = skip_option(&wire, &code, &full_len)) != NULL) {
if (code == opt_code) {
return position;
}
}
return NULL;
}
/*!
* \brief Add new EDNS option by replacing RDATA of OPT RR.
*
......@@ -282,69 +216,67 @@ int knot_edns_add_option(knot_rrset_t *opt_rr, uint16_t code,
return KNOT_EOK;
}
_public_
bool knot_edns_has_option(const knot_rrset_t *opt_rr, uint16_t code)
{
assert(opt_rr != NULL);
knot_rdata_t *rdata = knot_rdataset_at(&opt_rr->rrs, 0);
assert(rdata != NULL);
uint8_t *pos = find_option(rdata, code);
return pos != NULL;
}
_public_
uint8_t *knot_edns_get_option(const knot_rrset_t *opt_rr, uint16_t code)
{
assert(opt_rr != NULL);
if (opt_rr == NULL) {
return NULL;
}
knot_rdata_t *rdata = knot_rdataset_at(&opt_rr->rrs, 0);
assert(rdata != NULL);
return find_option(rdata, code);
}
_public_
uint16_t knot_edns_opt_get_code(const uint8_t *opt)
{
assert(opt != NULL);
if (rdata == NULL || rdata->len == 0) {
return NULL;
}
return knot_wire_read_u16(opt);
}
wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
_public_
uint16_t knot_edns_opt_get_length(const uint8_t *opt)
{
assert(opt != NULL);
while (wire_ctx_available(&wire) > 0 && wire.error == KNOT_EOK) {
uint8_t *pos = wire.position;
uint16_t opt_code = wire_ctx_read_u16(&wire);
uint16_t opt_len = wire_ctx_read_u16(&wire);
wire_ctx_skip(&wire, opt_len);
if (wire.error == KNOT_EOK && opt_code == code) {
return pos;
}
}
return knot_wire_read_u16(opt + sizeof(uint16_t));
return NULL;
}
_public_
bool knot_edns_check_record(knot_rrset_t *opt_rr)
int knot_edns_get_options(knot_rrset_t *opt_rr, knot_edns_options_t **out,
knot_mm_t *mm)
{
if (opt_rr->rrs.rr_count != 1) {
return false;
if (opt_rr == NULL || opt_rr->rrs.rr_count > 1 || out == NULL) {
return KNOT_EINVAL;
}
knot_rdata_t *rdata = knot_rdataset_at(&opt_rr->rrs, 0);
if (rdata == NULL) {
return false;
if (rdata == NULL || rdata->len == 0) {
return KNOT_EOK;
}
knot_edns_options_t *options = mm_calloc(mm, 1, sizeof(*options));
wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
/* RFC2671 4.4: {uint16_t code, uint16_t len, data} */
// read data to the end or error
while (wire_ctx_available(&wire) > 0 && wire.error == KNOT_EOK) {
wire_ctx_read_u16(&wire); // code
uint16_t opt_len = wire_ctx_read_u16(&wire); // length
wire_ctx_skip(&wire, opt_len); // data
uint8_t *pos = wire.position;
uint16_t opt_code = wire_ctx_read_u16(&wire);
uint16_t opt_len = wire_ctx_read_u16(&wire);
wire_ctx_skip(&wire, opt_len);
if (wire.error == KNOT_EOK && opt_code <= KNOT_EDNS_MAX_OPTION_CODE) {
options->ptr[opt_code] = pos;
}
}
if (wire.error != KNOT_EOK) {
mm_free(mm, options);
return wire.error;
}
return wire.error == KNOT_EOK;
*out = options;
return KNOT_EOK;
}
_public_
......
......@@ -29,6 +29,7 @@
#include "libknot/consts.h"
#include "libknot/rrset.h"
#include "libknot/wire.h"
/* Forward decls */
typedef struct knot_pkt knot_pkt_t;
......@@ -287,7 +288,6 @@ int knot_edns_reserve_option(knot_rrset_t *opt_rr, uint16_t code,
* \brief Adds EDNS Option to the OPT RR.
*
* \note The function now supports adding empty OPTION (just having its code).
* This does not make much sense now with NSID, but may be ok use later.
*
* \param opt_rr OPT RR structure to add the Option to.
* \param code Option code.
......@@ -301,21 +301,10 @@ int knot_edns_reserve_option(knot_rrset_t *opt_rr, uint16_t code,
int knot_edns_add_option(knot_rrset_t *opt_rr, uint16_t code,
uint16_t size, const uint8_t *data, knot_mm_t *mm);
/*!
* \brief Checks if the OPT RR contains Option with the specified code.
*
* \param opt_rr OPT RR structure to check for the Option in.
* \param code Option code to check for.
*
* \retval true if the OPT RR contains Option with Option code \a code.
* \retval false otherwise.
*/
bool knot_edns_has_option(const knot_rrset_t *opt_rr, uint16_t code);
/*!
* \brief Searches the OPT RR for option with the specified code.
*
* \param opt_rr OPT RR structure to search for the Option in.
* \param opt_rr OPT RR structure to search in.
* \param code Option code to search for.
*
* \retval pointer to option if found
......@@ -324,26 +313,51 @@ bool knot_edns_has_option(const knot_rrset_t *opt_rr, uint16_t code);
uint8_t *knot_edns_get_option(const knot_rrset_t *opt_rr, uint16_t code);
/*!
* \brief Returns the option code.
* \brief Pointers to every option in the OPT RR wire.
*/
typedef struct {
uint8_t *ptr[KNOT_EDNS_MAX_OPTION_CODE + 1];
} knot_edns_options_t;
/*!
* \brief Initializes pointers to options in a given OPT RR.
*
* \warning No safety checks are performed on the supplied data.
* \note If the OPT RR has no options, the output is NULL.
*
* \param opt_rr OPT RR structure to be used.
* \param out Structure to be initialized.
* \param mm Memory context.
*
* \return Error code, KNOT_EOK if successful.
*/
int knot_edns_get_options(knot_rrset_t *opt_rr, knot_edns_options_t **out,
knot_mm_t *mm);
/*!
* \brief Returns the option code.
*
* \param opt EDNS option (including code, length and data portion).
*
* \retval EDNS option code
* \return EDNS option code
*/
uint16_t knot_edns_opt_get_code(const uint8_t *opt);
static inline uint16_t knot_edns_opt_get_code(const uint8_t *opt)
{
assert(opt != NULL);
return knot_wire_read_u16(opt);
}
/*!
* \brief Returns the option data length.
*
* \warning No safety checks are performed on the supplied data.
*
* \param opt EDNS option (including code, length and data portion).
*
* \retval EDNS option length
* \return EDNS option length
*/
uint16_t knot_edns_opt_get_length(const uint8_t *opt);
static inline uint16_t knot_edns_opt_get_length(const uint8_t *opt)
{
assert(opt != NULL);
return knot_wire_read_u16(opt + sizeof(uint16_t));
}
/*!
* \brief Returns pointer to option data.
......@@ -359,17 +373,6 @@ static inline uint8_t *knot_edns_opt_get_data(uint8_t *opt)
return opt + KNOT_EDNS_OPTION_HDRLEN;
}
/*!
* \brief Checks OPT RR semantics.
*
* Checks whether RDATA are OK, i.e. that all OPTIONs have proper lengths.
*
* \param opt_rr OPT RR to check.
*
* \return true if passed, false if failed
*/
bool knot_edns_check_record(knot_rrset_t *opt_rr);
/*!
* \brief Computes a reasonable Padding data length for a given packet and opt RR.
*
......
......@@ -393,7 +393,7 @@ size_t knot_tsig_wire_maxsize(const knot_tsig_key_t *key)
}
_public_
int knot_tsig_rdata_is_ok(const knot_rrset_t *tsig)
bool knot_tsig_rdata_is_ok(const knot_rrset_t *tsig)
{
/*! \todo Check size, needs to check variable-length fields. */
return (tsig != NULL
......
......@@ -116,6 +116,6 @@ size_t knot_tsig_wire_size(const knot_tsig_key_t *key);
size_t knot_tsig_wire_maxsize(const knot_tsig_key_t *key);
/*! \todo Documentation. */
int knot_tsig_rdata_is_ok(const knot_rrset_t *tsig);
bool knot_tsig_rdata_is_ok(const knot_rrset_t *tsig);
/*! @} */
......@@ -737,7 +737,7 @@ static int process_query_packet(const knot_pkt_t *query,
// Prepare new query context.
query_t new_ctx = *query_ctx;
uint8_t *opt = knot_edns_get_option(reply->opt_rr, KNOT_EDNS_OPTION_COOKIE);
uint8_t *opt = knot_pkt_edns_option(reply, KNOT_EDNS_OPTION_COOKIE);
if (opt == NULL) {
ERR("bad cookie, missing EDNS section\n");
knot_pkt_free(reply);
......
......@@ -192,14 +192,14 @@ static void test_getters(knot_rrset_t *opt_rr)
total_size, actual_size);
/* NSID */
check = knot_edns_has_option(opt_rr, KNOT_EDNS_OPTION_NSID);
check = knot_edns_get_option(opt_rr, KNOT_EDNS_OPTION_NSID) != NULL;
ok(check, "OPT RR getters: NSID check");
/* Other OPTIONs */
check = knot_edns_has_option(opt_rr, E_OPT3_CODE);
check = knot_edns_get_option(opt_rr, E_OPT3_CODE) != NULL;
ok(check, "OPT RR getters: empty option 1");
check = knot_edns_has_option(opt_rr, E_OPT4_CODE);
check = knot_edns_get_option(opt_rr, E_OPT4_CODE) != NULL;
ok(check, "OPT RR getters: empty option 2");
uint16_t code = knot_edns_opt_get_code((const uint8_t *)"\x00\x0a" "\x00\x00");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment