summaryrefslogtreecommitdiffstats
path: root/src/libknot/packet/pkt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot/packet/pkt.c')
-rw-r--r--src/libknot/packet/pkt.c837
1 files changed, 837 insertions, 0 deletions
diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c
new file mode 100644
index 0000000..728bb3e
--- /dev/null
+++ b/src/libknot/packet/pkt.c
@@ -0,0 +1,837 @@
+/* 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "libknot/attribute.h"
+#include "libknot/packet/pkt.h"
+#include "libknot/codes.h"
+#include "libknot/descriptor.h"
+#include "libknot/errcode.h"
+#include "libknot/rrtype/tsig.h"
+#include "libknot/tsig-op.h"
+#include "libknot/packet/wire.h"
+#include "libknot/packet/rrset-wire.h"
+#include "libknot/wire.h"
+#include "contrib/mempattern.h"
+#include "contrib/wire_ctx.h"
+
+/*! \brief Packet RR array growth step. */
+#define NEXT_RR_ALIGN 16
+#define NEXT_RR_COUNT(count) (((count) / NEXT_RR_ALIGN + 1) * NEXT_RR_ALIGN)
+
+/*! \brief Scan packet for RRSet existence. */
+static bool pkt_contains(const knot_pkt_t *packet, const knot_rrset_t *rrset)
+{
+ assert(packet);
+ assert(rrset);
+
+ for (int i = 0; i < packet->rrset_count; ++i) {
+ const uint16_t type = packet->rr[i].type;
+ const knot_rdata_t *data = packet->rr[i].rrs.rdata;
+ if (type == rrset->type && data == rrset->rrs.rdata) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*! \brief Free all RRSets and reset RRSet count. */
+static void pkt_free_data(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ /* Free RRSets if applicable. */
+ for (uint16_t i = 0; i < pkt->rrset_count; ++i) {
+ if (pkt->rr_info[i].flags & KNOT_PF_FREE) {
+ knot_rrset_clear(&pkt->rr[i], &pkt->mm);
+ }
+ }
+ pkt->rrset_count = 0;
+
+ /* Free EDNS option positions. */
+ mm_free(&pkt->mm, pkt->edns_opts);
+ pkt->edns_opts = 0;
+}
+
+/*! \brief Allocate new wireformat of given length, assuming *pkt is zeroed. */
+static int pkt_wire_alloc(knot_pkt_t *pkt, uint16_t len)
+{
+ assert(pkt);
+
+ if (len < KNOT_WIRE_HEADER_SIZE) {
+ return KNOT_ERANGE;
+ }
+
+ pkt->wire = mm_alloc(&pkt->mm, len);
+ if (pkt->wire == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ pkt->flags |= KNOT_PF_FREE;
+ pkt->max_size = len;
+
+ /* Reset to header size. */
+ pkt->size = KNOT_WIRE_HEADER_SIZE;
+ memset(pkt->wire, 0, pkt->size);
+
+ return KNOT_EOK;
+}
+
+/*! \brief Set packet wireformat to an existing memory. */
+static void pkt_wire_set(knot_pkt_t *pkt, void *wire, uint16_t len)
+{
+ assert(pkt);
+
+ pkt->wire = wire;
+ pkt->size = pkt->max_size = len;
+ pkt->parsed = 0;
+}
+
+/*! \brief Calculate remaining size in the packet. */
+static uint16_t pkt_remaining(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ return pkt->max_size - pkt->size - pkt->reserved;
+}
+
+/*! \brief Return RR count for given section (from wire xxCOUNT in header). */
+static uint16_t pkt_rr_wirecount(knot_pkt_t *pkt, knot_section_t section_id)
+{
+ assert(pkt);
+ switch (section_id) {
+ case KNOT_ANSWER: return knot_wire_get_ancount(pkt->wire);
+ case KNOT_AUTHORITY: return knot_wire_get_nscount(pkt->wire);
+ case KNOT_ADDITIONAL: return knot_wire_get_arcount(pkt->wire);
+ default: assert(0); return 0;
+ }
+}
+
+/*! \brief Update RR count for given section (wire xxCOUNT in header). */
+static void pkt_rr_wirecount_add(knot_pkt_t *pkt, knot_section_t section_id,
+ int16_t val)
+{
+ assert(pkt);
+ switch (section_id) {
+ case KNOT_ANSWER: knot_wire_add_ancount(pkt->wire, val); break;
+ case KNOT_AUTHORITY: knot_wire_add_nscount(pkt->wire, val); break;
+ case KNOT_ADDITIONAL: knot_wire_add_arcount(pkt->wire, val); break;
+ }
+}
+
+/*! \brief Reserve enough space in the RR arrays. */
+static int pkt_rr_array_alloc(knot_pkt_t *pkt, uint16_t count)
+{
+ /* Enough space. */
+ if (pkt->rrset_allocd >= count) {
+ return KNOT_EOK;
+ }
+
+ /* Allocate rr_info and rr fields to next size. */
+ size_t next_size = NEXT_RR_COUNT(count);
+ knot_rrinfo_t *rr_info = mm_alloc(&pkt->mm, sizeof(knot_rrinfo_t) * next_size);
+ if (rr_info == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ knot_rrset_t *rr = mm_alloc(&pkt->mm, sizeof(knot_rrset_t) * next_size);
+ if (rr == NULL) {
+ mm_free(&pkt->mm, rr_info);
+ return KNOT_ENOMEM;
+ }
+
+ /* Copy and free the old data, if any. */
+ if (pkt->rrset_allocd > 0) {
+ memcpy(rr_info, pkt->rr_info, pkt->rrset_allocd * sizeof(knot_rrinfo_t));
+ memcpy(rr, pkt->rr, pkt->rrset_allocd * sizeof(knot_rrset_t));
+ mm_free(&pkt->mm, pkt->rr);
+ mm_free(&pkt->mm, pkt->rr_info);
+ }
+ pkt->rr = rr;
+ pkt->rr_info = rr_info;
+ pkt->rrset_allocd = next_size;
+
+ return KNOT_EOK;
+}
+
+static void compr_clear(knot_compr_t *compr)
+{
+ compr->rrinfo = NULL;
+ compr->suffix.pos = 0;
+ compr->suffix.labels = 0;
+}
+
+/*! \brief Clear the packet and switch wireformat pointers (possibly allocate new). */
+static int pkt_init(knot_pkt_t *pkt, void *wire, uint16_t len, knot_mm_t *mm)
+{
+ assert(pkt);
+
+ memset(pkt, 0, offsetof(knot_pkt_t, lower_qname));
+ pkt->lower_qname[0] = '\0';
+
+ /* No data to free, set memory context. */
+ memcpy(&pkt->mm, mm, sizeof(knot_mm_t));
+
+ /* Initialize wire. */
+ int ret = KNOT_EOK;
+ if (wire == NULL) {
+ ret = pkt_wire_alloc(pkt, len);
+ } else {
+ pkt_wire_set(pkt, wire, len);
+ }
+
+ /* Initialize compression context (zeroed above). */
+ pkt->compr.wire = pkt->wire;
+
+ return ret;
+}
+
+/*! \brief Reset packet parse state. */
+static void sections_reset(knot_pkt_t *pkt)
+{
+ pkt->current = KNOT_ANSWER;
+ memset(pkt->sections, 0, sizeof(pkt->sections));
+ (void)knot_pkt_begin(pkt, KNOT_ANSWER);
+}
+
+/*! \brief Allocate new packet using memory context. */
+static knot_pkt_t *pkt_new_mm(void *wire, uint16_t len, knot_mm_t *mm)
+{
+ assert(mm);
+
+ knot_pkt_t *pkt = mm_alloc(mm, sizeof(knot_pkt_t));
+ if (pkt == NULL) {
+ return NULL;
+ }
+
+ if (pkt_init(pkt, wire, len, mm) != KNOT_EOK) {
+ mm_free(mm, pkt);
+ return NULL;
+ }
+
+ return pkt;
+}
+
+_public_
+knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm)
+{
+ /* Default memory allocator if NULL. */
+ knot_mm_t _mm;
+ if (mm == NULL) {
+ mm_ctx_init(&_mm);
+ mm = &_mm;
+ }
+
+ return pkt_new_mm(wire, len, mm);
+}
+
+static int append_tsig(knot_pkt_t *dst, const knot_pkt_t *src)
+{
+ /* Check if a wire TSIG is available. */
+ if (src->tsig_wire.pos != NULL) {
+ if (dst->max_size < src->size + src->tsig_wire.len) {
+ return KNOT_ESPACE;
+ }
+ memcpy(dst->wire + dst->size, src->tsig_wire.pos,
+ src->tsig_wire.len);
+ dst->size += src->tsig_wire.len;
+
+ /* Increment arcount. */
+ knot_wire_set_arcount(dst->wire,
+ knot_wire_get_arcount(dst->wire) + 1);
+ } else {
+ return knot_tsig_append(dst->wire, &dst->size, dst->max_size,
+ src->tsig_rr);
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src)
+{
+ if (dst == NULL || src == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (dst->max_size < src->size) {
+ return KNOT_ESPACE;
+ }
+ memcpy(dst->wire, src->wire, src->size);
+ dst->size = src->size;
+
+ /* Append TSIG record. */
+ if (src->tsig_rr) {
+ int ret = append_tsig(dst, src);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ /* Invalidate arrays. */
+ dst->rr = NULL;
+ dst->rr_info = NULL;
+ dst->rrset_count = 0;
+ dst->rrset_allocd = 0;
+
+ /* @note This could be done more effectively if needed. */
+ return knot_pkt_parse(dst, 0);
+}
+
+static void payload_clear(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ /* Keep question. */
+ pkt->parsed = 0;
+ pkt->reserved = 0;
+
+ /* Free RRSets if applicable. */
+ pkt_free_data(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_
+int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query)
+{
+ if (pkt == NULL || query == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Header + question size. */
+ size_t base_size = KNOT_WIRE_HEADER_SIZE + knot_pkt_question_size(query);
+ if (base_size > pkt->max_size) {
+ return KNOT_ESPACE;
+ }
+
+ pkt->size = base_size;
+ memcpy(pkt->wire, query->wire, base_size);
+
+ /* Copy lowercased QNAME. */
+ pkt->qname_size = query->qname_size;
+ if (query->qname_size == 0) {
+ /* Reset question count if malformed. */
+ knot_wire_set_qdcount(pkt->wire, 0);
+ }
+ memcpy(pkt->lower_qname, query->lower_qname, pkt->qname_size);
+
+ /* Update flags and section counters. */
+ knot_wire_set_ancount(pkt->wire, 0);
+ knot_wire_set_nscount(pkt->wire, 0);
+ knot_wire_set_arcount(pkt->wire, 0);
+
+ knot_wire_set_qr(pkt->wire);
+ knot_wire_clear_tc(pkt->wire);
+ knot_wire_clear_ad(pkt->wire);
+ knot_wire_clear_ra(pkt->wire);
+ knot_wire_clear_aa(pkt->wire);
+ knot_wire_clear_z(pkt->wire);
+
+ /* Clear payload. */
+ payload_clear(pkt);
+
+ /* Clear compression context. */
+ compr_clear(&pkt->compr);
+
+ return KNOT_EOK;
+}
+
+_public_
+void knot_pkt_clear(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return;
+ }
+
+ /* Reset to header size. */
+ pkt->size = KNOT_WIRE_HEADER_SIZE;
+ memset(pkt->wire, 0, pkt->size);
+
+ /* Clear payload. */
+ payload_clear(pkt);
+
+ /* Clear compression context. */
+ compr_clear(&pkt->compr);
+
+ /* Initialize lowercased QNAME. */
+ pkt->lower_qname[0] = '\0';
+}
+
+_public_
+void knot_pkt_free(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return;
+ }
+
+ /* Free temporary RRSets. */
+ pkt_free_data(pkt);
+
+ /* Free RR/RR info arrays. */
+ mm_free(&pkt->mm, pkt->rr);
+ mm_free(&pkt->mm, pkt->rr_info);
+
+ /* Free the space for wireformat. */
+ if (pkt->flags & KNOT_PF_FREE) {
+ mm_free(&pkt->mm, pkt->wire);
+ }
+
+ mm_free(&pkt->mm, pkt);
+}
+
+_public_
+int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reserve extra space (if possible). */
+ if (pkt_remaining(pkt) >= size) {
+ pkt->reserved += size;
+ return KNOT_EOK;
+ } else {
+ return KNOT_ERANGE;
+ }
+}
+
+_public_
+int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (pkt->reserved >= size) {
+ pkt->reserved -= size;
+ return KNOT_EOK;
+ } else {
+ return KNOT_ERANGE;
+ }
+}
+
+_public_
+int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id)
+{
+ if (pkt == NULL || section_id < pkt->current) {
+ return KNOT_EINVAL;
+ }
+
+ /* Remember watermark but not on repeated calls. */
+ pkt->sections[section_id].pkt = pkt;
+ if (section_id > pkt->current) {
+ pkt->sections[section_id].pos = pkt->rrset_count;
+ }
+
+ pkt->current = section_id;
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype)
+{
+ if (pkt == NULL || qname == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(pkt->size == KNOT_WIRE_HEADER_SIZE);
+ assert(pkt->rrset_count == 0);
+
+ /* Copy name into wire format buffer. */
+ wire_ctx_t wire = wire_ctx_init(pkt->wire, pkt->max_size);
+ wire_ctx_set_offset(&wire, KNOT_WIRE_HEADER_SIZE);
+
+ int qname_len = knot_dname_to_wire(wire.position,
+ qname, wire_ctx_available(&wire));
+ if (qname_len < 0) {
+ return qname_len;
+ }
+ wire_ctx_skip(&wire, qname_len);
+
+ /* Copy QNAME and canonicalize to lowercase. */
+ knot_dname_copy_lower(pkt->lower_qname, qname);
+
+ /* Copy QTYPE & QCLASS */
+ wire_ctx_write_u16(&wire, qtype);
+ wire_ctx_write_u16(&wire, qclass);
+
+ /* Check errors. */
+ if (wire.error != KNOT_EOK) {
+ return wire.error;
+ }
+
+ /* Update question count and sizes. */
+ knot_wire_set_qdcount(pkt->wire, 1);
+ pkt->size = wire_ctx_offset(&wire);
+ pkt->qname_size = qname_len;
+
+ /* Start writing ANSWER. */
+ return knot_pkt_begin(pkt, KNOT_ANSWER);
+}
+
+_public_
+int knot_pkt_put_rotate(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr,
+ uint16_t rotate, uint16_t flags)
+{
+ if (pkt == NULL || rr == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reserve memory for RR descriptors. */
+ int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Check for double insertion. */
+ if ((flags & KNOT_PF_CHECKDUP) && pkt_contains(pkt, rr)) {
+ return KNOT_EOK;
+ }
+
+ knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count];
+ memset(rrinfo, 0, sizeof(knot_rrinfo_t));
+ rrinfo->pos = pkt->size;
+ rrinfo->flags = flags;
+ rrinfo->compress_ptr[0] = compr_hint;
+ memcpy(pkt->rr + pkt->rrset_count, rr, sizeof(knot_rrset_t));
+
+ /* Disable compression if no QNAME is available. */
+ knot_compr_t *compr = NULL;
+ if (knot_pkt_qname(pkt) != NULL) {
+ /* Initialize compression context if it did not happen yet. */
+ pkt->compr.rrinfo = rrinfo;
+ if (pkt->compr.suffix.pos == 0) {
+ pkt->compr.suffix.pos = KNOT_WIRE_HEADER_SIZE;
+ pkt->compr.suffix.labels =
+ knot_dname_labels(pkt->compr.wire + pkt->compr.suffix.pos,
+ pkt->compr.wire);
+ }
+
+ compr = &pkt->compr;
+ }
+
+ uint8_t *pos = pkt->wire + pkt->size;
+ size_t maxlen = pkt_remaining(pkt);
+
+ /* Write RRSet to wireformat. */
+ ret = knot_rrset_to_wire_extra(rr, pos, maxlen, rotate, compr, flags);
+ if (ret < 0) {
+ /* Truncate packet if required. */
+ if (ret == KNOT_ESPACE && !(flags & KNOT_PF_NOTRUNC)) {
+ knot_wire_set_tc(pkt->wire);
+ }
+ return ret;
+ }
+
+ size_t len = ret;
+ uint16_t rr_added = rr->rrs.count;
+
+ /* Keep reference to special types. */
+ if (rr->type == KNOT_RRTYPE_OPT) {
+ pkt->opt_rr = &pkt->rr[pkt->rrset_count];
+ }
+
+ if (rr_added > 0) {
+ pkt->rrset_count += 1;
+ pkt->sections[pkt->current].count += 1;
+ pkt->size += len;
+ pkt_rr_wirecount_add(pkt, pkt->current, rr_added);
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_parse_question(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Check at least header size. */
+ if (pkt->size < KNOT_WIRE_HEADER_SIZE) {
+ return KNOT_EMALF;
+ }
+
+ /* We have at least some DNS header. */
+ pkt->parsed = KNOT_WIRE_HEADER_SIZE;
+
+ /* Check QD count. */
+ uint16_t qd = knot_wire_get_qdcount(pkt->wire);
+ if (qd > 1) {
+ return KNOT_EMALF;
+ }
+
+ /* No question. */
+ if (qd == 0) {
+ pkt->qname_size = 0;
+ return KNOT_EOK;
+ }
+
+ /* Process question. */
+ int len = knot_dname_wire_check(pkt->wire + pkt->parsed,
+ pkt->wire + pkt->size,
+ NULL /* No compression in QNAME. */);
+ if (len <= 0) {
+ return KNOT_EMALF;
+ }
+
+ /* Check QCLASS/QTYPE size. */
+ uint16_t question_size = len + 2 * sizeof(uint16_t); /* QCLASS + QTYPE */
+ if (pkt->parsed + question_size > pkt->size) {
+ return KNOT_EMALF;
+ }
+
+ pkt->parsed += question_size;
+ pkt->qname_size = len;
+
+ /* Copy QNAME and canonicalize to lowercase. */
+ knot_dname_copy_lower(pkt->lower_qname, pkt->wire + KNOT_WIRE_HEADER_SIZE);
+
+ return KNOT_EOK;
+}
+
+/*! \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)
+{
+ switch (rr->type) {
+ case KNOT_RRTYPE_TSIG:
+ 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)) {
+ pkt->parsed -= rr_size;
+ pkt->size -= rr_size;
+ pkt->tsig_wire.pos = pkt->wire + pkt->parsed;
+ 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:
+ 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:
+ break;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_rr(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+
+ if (pkt->parsed >= pkt->size) {
+ return KNOT_EFEWDATA;
+ }
+
+ /* Reserve memory for RR descriptors. */
+ int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Initialize RR info. */
+ memset(&pkt->rr_info[pkt->rrset_count], 0, sizeof(knot_rrinfo_t));
+ pkt->rr_info[pkt->rrset_count].pos = pkt->parsed;
+ pkt->rr_info[pkt->rrset_count].flags = KNOT_PF_FREE;
+
+ /* Parse wire format. */
+ size_t rr_size = pkt->parsed;
+ knot_rrset_t *rr = &pkt->rr[pkt->rrset_count];
+ ret = knot_rrset_rr_from_wire(pkt->wire, &pkt->parsed, pkt->size,
+ rr, &pkt->mm, !(flags & KNOT_PF_NOCANON));
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Calculate parsed RR size from before/after parsing. */
+ rr_size = (pkt->parsed - rr_size);
+
+ /* Update packet RRSet count. */
+ ++pkt->rrset_count;
+ ++pkt->sections[pkt->current].count;
+
+ /* Check special RRs (OPT and TSIG). */
+ return check_rr_constraints(pkt, rr, rr_size, flags);
+}
+
+static int parse_section(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+
+ 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) {
+ int ret = parse_rr(pkt, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_payload(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+ assert(pkt->wire);
+ assert(pkt->size > 0);
+
+ /* Reserve memory in advance to avoid resizing. */
+ size_t rr_count = knot_wire_get_ancount(pkt->wire) +
+ knot_wire_get_nscount(pkt->wire) +
+ knot_wire_get_arcount(pkt->wire);
+
+ if (rr_count > pkt->size / KNOT_WIRE_RR_MIN_SIZE) {
+ return KNOT_EMALF;
+ }
+
+ int ret = pkt_rr_array_alloc(pkt, rr_count);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) {
+ ret = knot_pkt_begin(pkt, i);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ ret = parse_section(pkt, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ /* TSIG must be last record of AR if present. */
+ const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL);
+ if (pkt->tsig_rr != NULL) {
+ const knot_rrset_t *last_rr = knot_pkt_rr(ar, ar->count - 1);
+ if (ar->count > 0 && pkt->tsig_rr->rrs.rdata != last_rr->rrs.rdata) {
+ return KNOT_EMALF;
+ }
+ }
+
+ /* Check for trailing garbage. */
+ if (pkt->parsed < pkt->size) {
+ return KNOT_ETRAIL;
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reset parse state. */
+ sections_reset(pkt);
+
+ int ret = knot_pkt_parse_question(pkt);
+ if (ret == KNOT_EOK) {
+ ret = parse_payload(pkt, flags);
+ }
+
+ return ret;
+}
+
+_public_
+uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return 0;
+ }
+
+ /* Get header RCODE. */
+ uint16_t rcode = knot_wire_get_rcode(pkt->wire);
+
+ /* Update to extended RCODE if EDNS is available. */
+ if (pkt->opt_rr != NULL) {
+ uint8_t opt_rcode = knot_edns_get_ext_rcode(pkt->opt_rr);
+ rcode = knot_edns_whole_rcode(opt_rcode, rcode);
+ }
+
+ /* Return if not NOTAUTH. */
+ if (rcode != KNOT_RCODE_NOTAUTH) {
+ return rcode;
+ }
+
+ /* Get TSIG RCODE. */
+ uint16_t tsig_rcode = KNOT_RCODE_NOERROR;
+ if (pkt->tsig_rr != NULL) {
+ tsig_rcode = knot_tsig_rdata_error(pkt->tsig_rr);
+ }
+
+ /* Return proper RCODE. */
+ if (tsig_rcode != KNOT_RCODE_NOERROR) {
+ return tsig_rcode;
+ } else {
+ return rcode;
+ }
+}
+
+_public_
+const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return "";
+ }
+
+ uint16_t rcode = knot_pkt_ext_rcode(pkt);
+
+ const knot_lookup_t *item = NULL;
+ if (pkt->tsig_rr != NULL) {
+ item = knot_lookup_by_id(knot_tsig_rcode_names, rcode);
+ }
+ if (item == NULL) {
+ item = knot_lookup_by_id(knot_rcode_names, rcode);
+ }
+
+ return (item != NULL) ? item->name : "";
+}