diff options
Diffstat (limited to 'src/libknot/edns.c')
-rw-r--r-- | src/libknot/edns.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/src/libknot/edns.c b/src/libknot/edns.c new file mode 100644 index 0000000..05ebd7b --- /dev/null +++ b/src/libknot/edns.c @@ -0,0 +1,416 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> + +#include "edns.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/debug.h" +#include "util/error.h" + +/*! \brief Various EDNS constatns. */ +enum knot_edns_consts { + /*! \brief Mask for the DO bit. */ + KNOT_EDNS_DO_MASK = (uint16_t)0x8000, + /*! \brief Step for allocation of space for option entries. */ + KNOT_EDNS_OPTION_STEP = 1 +}; + +/*! \brief Minimum size of EDNS OPT RR in wire format. */ +static const short KNOT_EDNS_MIN_SIZE = 11; + +/*----------------------------------------------------------------------------*/ + +knot_opt_rr_t *knot_edns_new() +{ + knot_opt_rr_t *opt_rr = (knot_opt_rr_t *)malloc( + sizeof(knot_opt_rr_t)); + CHECK_ALLOC_LOG(opt_rr, NULL); + opt_rr->size = KNOT_EDNS_MIN_SIZE; + opt_rr->option_count = 0; + opt_rr->options_max = 0; + + opt_rr->ext_rcode = 0; + opt_rr->flags = 0; + opt_rr->version = 0; + + return opt_rr; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_wire(knot_opt_rr_t *opt_rr, const uint8_t *wire, + size_t max_size) +{ + const uint8_t *pos = wire; + int parsed = 0; + + if (pos == NULL || max_size == 0 || opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (max_size < KNOT_EDNS_MIN_SIZE) { + dbg_edns("Not enough data to parse OPT RR header.\n"); + return KNOT_EFEWDATA; + } + + // owner of EDNS OPT RR must be root (0) + if (*pos != 0) { + dbg_edns("EDNS packet malformed (expected root " + "domain as owner).\n"); + return KNOT_EMALF; + } + pos += 1; + + // check the type of the record (must be OPT) + if (knot_wire_read_u16(pos) != KNOT_RRTYPE_OPT) { + dbg_edns("EDNS packet malformed (expected OPT type" + ".\n"); + return KNOT_EMALF; + } + pos += 2; + + opt_rr->payload = knot_wire_read_u16(pos); + dbg_edns("Parsed payload: %u\n", opt_rr->payload); + + pos += 2; + opt_rr->ext_rcode = *(pos++); + opt_rr->version = *(pos++); + opt_rr->flags = knot_wire_read_u16(pos); + pos += 2; + + parsed = KNOT_EDNS_MIN_SIZE; + + // ignore RDATA, but move pos behind them + uint16_t rdlength = knot_wire_read_u16(pos); + pos += 2; + + if (max_size - parsed < rdlength) { + dbg_edns("Not enough data to parse OPT RR.\n"); + return KNOT_EFEWDATA; + } + + while (parsed < rdlength + KNOT_EDNS_MIN_SIZE) { + if (max_size - parsed < 4) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION header.\n"); + return KNOT_EFEWDATA; + } + uint16_t code = knot_wire_read_u16(pos); + pos += 2; + uint16_t length = knot_wire_read_u16(pos); + pos += 2; + dbg_edns("EDNS OPTION: Code: %u, Length: %u\n", + code, length); + if (max_size - parsed - 4 < length) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION data.\n"); + return KNOT_EFEWDATA; + } + int ret; + if ((ret = + knot_edns_add_option(opt_rr, code, length, pos)) != 0) { + dbg_edns("Error parsing OPT option field.\n"); + return ret; + } + pos += length; + parsed += length + 4; + } + + return parsed; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, + const knot_rrset_t *rrset) +{ + if (opt_rr == NULL || rrset == NULL + || knot_rrset_type(rrset) != KNOT_RRTYPE_OPT) { + return KNOT_EBADARG; + } + + dbg_edns("Parsing payload.\n"); + opt_rr->payload = knot_rrset_class(rrset); + + // the TTL has switched bytes + uint32_t ttl; + dbg_edns("TTL: %u\n", knot_rrset_ttl(rrset)); + knot_wire_write_u32((uint8_t *)&ttl, knot_rrset_ttl(rrset)); + // first byte of TTL is extended RCODE + dbg_edns("TTL: %u\n", ttl); + memcpy(&opt_rr->ext_rcode, &ttl, 1); + dbg_edns("Parsed extended RCODE: %u.\n", opt_rr->ext_rcode); + // second is the version + memcpy(&opt_rr->version, (const uint8_t *)(&ttl) + 1, 1); + dbg_edns("Parsed version: %u.\n", opt_rr->version); + // third and fourth are flags + opt_rr->flags = knot_wire_read_u16((const uint8_t *)(&ttl) + 2); + dbg_edns("Parsed flags: %u.\n", opt_rr->flags); + // size of the header, options are counted elsewhere + opt_rr->size = 11; + + int rc = 0; + dbg_edns("Parsing options.\n"); + const knot_rdata_t *rdata = knot_rrset_rdata(rrset); + + // in OPT RR, all RDATA are in one RDATA item stored as BINARY data, + // i.e. preceded by their length + if (rdata != NULL) { + assert(knot_rdata_item_count(rdata) == 1); + const uint8_t *raw = (const uint8_t *) + knot_rdata_item(rdata, 0)->raw_data; + uint16_t size = knot_wire_read_u16(raw); + int pos = 2; + assert(size > 0); + while (pos - 2 < size) { + // ensure there is enough data to parse the OPTION CODE + // and OPTION LENGTH + if (size - pos + 2 < 4) { + return KNOT_EMALF; + } + uint16_t opt_code = knot_wire_read_u16(raw + pos); + uint16_t opt_size = knot_wire_read_u16(raw + pos + 2); + + // there should be enough data for parsing the OPTION + // data + if (size - pos - 2 < opt_size) { + return KNOT_EMALF; + } + rc = knot_edns_add_option(opt_rr, opt_code, opt_size, + raw + pos + 4); + if (rc != KNOT_EOK) { + return rc; + } + pos += 4 + opt_size; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_payload(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->payload; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_payload(knot_opt_rr_t *opt_rr, + uint16_t payload) +{ + assert(opt_rr != NULL); + opt_rr->payload = payload; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_ext_rcode(const knot_opt_rr_t *opt_rr) +{ + return opt_rr->ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_ext_rcode(knot_opt_rr_t *opt_rr, + uint8_t ext_rcode) +{ + assert(opt_rr != NULL); + opt_rr->ext_rcode = ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_version(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->version; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_version(knot_opt_rr_t *opt_rr, + uint8_t version) +{ + assert(opt_rr != NULL); + opt_rr->version = version; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_flags(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->flags; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_do(const knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + dbg_edns("Flags: %u\n", opt_rr->flags); + return (opt_rr->flags & KNOT_EDNS_DO_MASK); +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_do(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return; + } + + opt_rr->flags |= KNOT_EDNS_DO_MASK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_add_option(knot_opt_rr_t *opt_rr, uint16_t code, + uint16_t length, const uint8_t *data) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (opt_rr->option_count == opt_rr->options_max) { + knot_opt_option_t *options_new = + (knot_opt_option_t *)calloc( + (opt_rr->options_max + KNOT_EDNS_OPTION_STEP), + sizeof(knot_opt_option_t)); + CHECK_ALLOC_LOG(options_new, KNOT_ENOMEM); + memcpy(options_new, opt_rr->options, opt_rr->option_count); + opt_rr->options = options_new; + opt_rr->options_max += KNOT_EDNS_OPTION_STEP; + } + + dbg_edns("Adding option.\n"); + dbg_edns("Code: %u.\n", code); + dbg_edns("Length: %u.\n", length); + dbg_edns("Data: %p.\n", data); + + opt_rr->options[opt_rr->option_count].data = (uint8_t *)malloc(length); + CHECK_ALLOC_LOG(opt_rr->options[opt_rr->option_count].data, KNOT_ENOMEM); + memcpy(opt_rr->options[opt_rr->option_count].data, data, length); + + opt_rr->options[opt_rr->option_count].code = code; + opt_rr->options[opt_rr->option_count].length = length; + + ++opt_rr->option_count; + opt_rr->size += 4 + length; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_has_option(const knot_opt_rr_t *opt_rr, uint16_t code) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + int i = 0; + while (i < opt_rr->option_count && opt_rr->options[i].code != code) { + ++i; + } + + assert(i >= opt_rr->option_count || opt_rr->options[i].code == code); + + return (i < opt_rr->option_count); +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_to_wire(const knot_opt_rr_t *opt_rr, uint8_t *wire, + size_t max_size) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + assert(KNOT_EDNS_MIN_SIZE <= max_size); + + if (max_size < opt_rr->size) { + dbg_edns("Not enough place for OPT RR wire format.\n"); + return KNOT_ESPACE; + } + + uint8_t *pos = wire; + *(pos++) = 0; + knot_wire_write_u16(pos, KNOT_RRTYPE_OPT); + pos += 2; + knot_wire_write_u16(pos, opt_rr->payload); + pos += 2; + *(pos++) = opt_rr->ext_rcode; + *(pos++) = opt_rr->version; + knot_wire_write_u16(pos, opt_rr->flags); + pos += 2; + + uint8_t *rdlen = pos; + uint16_t len = 0; + pos += 2; + + // OPTIONs + for (int i = 0; i < opt_rr->option_count; ++i) { + knot_wire_write_u16(pos, opt_rr->options[i].code); + pos += 2; + knot_wire_write_u16(pos, opt_rr->options[i].length); + pos += 2; + memcpy(pos, opt_rr->options[i].data, opt_rr->options[i].length); + pos += opt_rr->options[i].length; + len += 4 + opt_rr->options[i].length; + } + + knot_wire_write_u16(rdlen, len); + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_size(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_free(knot_opt_rr_t **opt_rr) +{ + if (opt_rr == NULL || *opt_rr == NULL) { + return; + } + + if ((*opt_rr)->option_count > 0) { + free((*opt_rr)->options); + } + free(*opt_rr); + *opt_rr = NULL; +} |