diff options
Diffstat (limited to 'src/libknot')
81 files changed, 21176 insertions, 0 deletions
diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc new file mode 100644 index 0000000..6fe35f4 --- /dev/null +++ b/src/libknot/Makefile.inc @@ -0,0 +1,97 @@ +lib_LTLIBRARIES += libknot.la +pkgconfig_DATA += libknot.pc + +libknot_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(lmdb_CFLAGS) +libknot_la_LDFLAGS = $(AM_LDFLAGS) $(libknot_VERSION_INFO) $(LDFLAG_EXCLUDE_LIBS) +libknot_la_LIBADD = libcontrib.la libdnssec.la $(math_LIBS) $(lmdb_LIBS) + +include_libknotdir = $(includedir) +nobase_include_libknot_HEADERS = \ + libknot/attribute.h \ + libknot/codes.h \ + libknot/consts.h \ + libknot/control/control.h \ + libknot/cookies.h \ + libknot/descriptor.h \ + libknot/dname.h \ + libknot/endian.h \ + libknot/errcode.h \ + libknot/error.h \ + libknot/libknot.h \ + libknot/lookup.h \ + libknot/mm_ctx.h \ + libknot/db/db.h \ + libknot/db/db_lmdb.h \ + libknot/db/db_trie.h \ + libknot/packet/compr.h \ + libknot/packet/pkt.h \ + libknot/packet/rrset-wire.h \ + libknot/packet/wire.h \ + libknot/rdata.h \ + libknot/rdataset.h \ + libknot/rrset-dump.h \ + libknot/rrset.h \ + libknot/rrtype/dnskey.h \ + libknot/rrtype/ds.h \ + libknot/rrtype/naptr.h \ + libknot/rrtype/nsec.h \ + libknot/rrtype/nsec3.h \ + libknot/rrtype/nsec3param.h \ + libknot/rrtype/opt.h \ + libknot/rrtype/rdname.h \ + libknot/rrtype/rrsig.h \ + libknot/rrtype/soa.h \ + libknot/rrtype/tsig.h \ + libknot/tsig-op.h \ + libknot/tsig.h \ + libknot/wire.h \ + libknot/yparser/yparser.h \ + libknot/yparser/ypformat.h \ + libknot/yparser/ypschema.h \ + libknot/yparser/yptrafo.h \ + libknot/version.h + +libknot_la_SOURCES = \ + libknot/codes.c \ + libknot/control/control.c \ + libknot/cookies.c \ + libknot/descriptor.c \ + libknot/dname.c \ + libknot/error.c \ + libknot/db/db_lmdb.c \ + libknot/db/db_trie.c \ + libknot/packet/pkt.c \ + libknot/packet/rrset-wire.c \ + libknot/rdataset.c \ + libknot/rrset-dump.c \ + libknot/rrset.c \ + libknot/rrtype/naptr.c \ + libknot/rrtype/opt.c \ + libknot/rrtype/tsig.c \ + libknot/tsig-op.c \ + libknot/tsig.c \ + libknot/yparser/yparser.c \ + libknot/yparser/ypbody.c \ + libknot/yparser/ypformat.c \ + libknot/yparser/ypschema.c \ + libknot/yparser/yptrafo.c + +if ENABLE_XDP +libknot_la_CPPFLAGS += $(libbpf_CFLAGS) +libknot_la_LIBADD += $(libbpf_LIBS) + +nobase_include_libknot_HEADERS += \ + libknot/xdp/bpf-consts.h \ + libknot/xdp/eth.h \ + libknot/xdp/xdp.h + +libknot_la_SOURCES += \ + libknot/xdp/bpf-kernel-obj.c \ + libknot/xdp/bpf-kernel-obj.h \ + libknot/xdp/bpf-user.c \ + libknot/xdp/bpf-user.h \ + libknot/xdp/eth.c \ + libknot/xdp/xdp.c +endif ENABLE_XDP + +DIST_SUBDIRS = libknot/xdp diff --git a/src/libknot/attribute.h b/src/libknot/attribute.h new file mode 100644 index 0000000..d494ce3 --- /dev/null +++ b/src/libknot/attribute.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2014 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/>. + */ + +/*! + * \file + * + * \brief Function attributes. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +/*! \brief Library visibility macros. */ +#define _public_ __attribute__((visibility("default"))) +#define _hidden_ __attribute__((visibility("hidden"))) + +/*! \brief GNU C function attributes. */ +#if __GNUC__ >= 3 +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _noreturn_ __attribute__ ((noreturn)) +#define _malloc_ __attribute__ ((malloc)) +#define _mustcheck_ __attribute__ ((warn_unused_result)) +#else +#define _pure_ +#define _const_ +#define _noreturn_ +#define _malloc_ +#define _mustcheck_ +#endif + +/*! @} */ diff --git a/src/libknot/codes.c b/src/libknot/codes.c new file mode 100644 index 0000000..44d80ad --- /dev/null +++ b/src/libknot/codes.c @@ -0,0 +1,81 @@ +/* Copyright (C) 2020 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 "libknot/attribute.h" +#include "libknot/codes.h" +#include "libknot/consts.h" + +_public_ +const knot_lookup_t knot_opcode_names[] = { + { KNOT_OPCODE_QUERY, "QUERY" }, + { KNOT_OPCODE_IQUERY, "IQUERY" }, + { KNOT_OPCODE_STATUS, "STATUS" }, + { KNOT_OPCODE_NOTIFY, "NOTIFY" }, + { KNOT_OPCODE_UPDATE, "UPDATE" }, + { 0, NULL } +}; + +_public_ +const knot_lookup_t knot_rcode_names[] = { + { KNOT_RCODE_NOERROR, "NOERROR" }, + { KNOT_RCODE_FORMERR, "FORMERR" }, + { KNOT_RCODE_SERVFAIL, "SERVFAIL" }, + { KNOT_RCODE_NXDOMAIN, "NXDOMAIN" }, + { KNOT_RCODE_NOTIMPL, "NOTIMPL" }, + { KNOT_RCODE_REFUSED, "REFUSED" }, + { KNOT_RCODE_YXDOMAIN, "YXDOMAIN" }, + { KNOT_RCODE_YXRRSET, "YXRRSET" }, + { KNOT_RCODE_NXRRSET, "NXRRSET" }, + { KNOT_RCODE_NOTAUTH, "NOTAUTH" }, + { KNOT_RCODE_NOTZONE, "NOTZONE" }, + { KNOT_RCODE_BADVERS, "BADVERS" }, + { KNOT_RCODE_BADKEY, "BADKEY" }, + { KNOT_RCODE_BADTIME, "BADTIME" }, + { KNOT_RCODE_BADMODE, "BADMODE" }, + { KNOT_RCODE_BADNAME, "BADNAME" }, + { KNOT_RCODE_BADALG, "BADALG" }, + { KNOT_RCODE_BADTRUNC, "BADTRUNC" }, + { KNOT_RCODE_BADCOOKIE, "BADCOOKIE" }, + { 0, NULL } +}; + +_public_ +const knot_lookup_t knot_tsig_rcode_names[] = { + { KNOT_RCODE_BADSIG, "BADSIG" }, + { 0, NULL } +}; + +_public_ +const knot_lookup_t knot_dnssec_alg_names[] = { + { KNOT_DNSSEC_ALG_DELETE, "DELETE" }, + { KNOT_DNSSEC_ALG_RSAMD5, "RSAMD5" }, + { KNOT_DNSSEC_ALG_DH, "DH" }, + { KNOT_DNSSEC_ALG_DSA, "DSA" }, + { KNOT_DNSSEC_ALG_RSASHA1, "RSASHA1" }, + { KNOT_DNSSEC_ALG_DSA_NSEC3_SHA1, "DSA_NSEC3_SHA1" }, + { KNOT_DNSSEC_ALG_RSASHA1_NSEC3_SHA1, "RSASHA1_NSEC3_SHA1" }, + { KNOT_DNSSEC_ALG_RSASHA256, "RSASHA256" }, + { KNOT_DNSSEC_ALG_RSASHA512, "RSASHA512" }, + { KNOT_DNSSEC_ALG_ECC_GOST, "ECC_GOST" }, + { KNOT_DNSSEC_ALG_ECDSAP256SHA256, "ECDSAP256SHA256" }, + { KNOT_DNSSEC_ALG_ECDSAP384SHA384, "ECDSAP384SHA384" }, + { KNOT_DNSSEC_ALG_ED25519, "ED25519" }, + { KNOT_DNSSEC_ALG_ED448, "ED448" }, + { KNOT_DNSSEC_ALG_INDIRECT, "INDIRECT" }, + { KNOT_DNSSEC_ALG_PRIVATEDNS, "PRIVATEDNS" }, + { KNOT_DNSSEC_ALG_PRIVATEOID, "PRIVATEOID" }, + { 0, NULL } +}; diff --git a/src/libknot/codes.h b/src/libknot/codes.h new file mode 100644 index 0000000..1384d8d --- /dev/null +++ b/src/libknot/codes.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2016 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/>. + */ + +/*! + * \file + * + * \brief Some DNS-related code names. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include "libknot/lookup.h" + +/*! + * \brief DNS operation code names. + */ +extern const knot_lookup_t knot_opcode_names[]; + +/*! + * \brief DNS reply code names. + */ +extern const knot_lookup_t knot_rcode_names[]; + +/*! + * \brief TSIG exceptions to reply code names. + */ +extern const knot_lookup_t knot_tsig_rcode_names[]; + +/*! + * \brief DNSSEC algorithm names. + */ +extern const knot_lookup_t knot_dnssec_alg_names[]; + +/*! @} */ diff --git a/src/libknot/consts.h b/src/libknot/consts.h new file mode 100644 index 0000000..4154cc4 --- /dev/null +++ b/src/libknot/consts.h @@ -0,0 +1,159 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Some DNS-related constants. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +/*! + * \brief Basic limits for domain names (RFC 1035). + */ +#define KNOT_DNAME_MAXLEN 255 /*!< 1-byte maximum. */ +#define KNOT_DNAME_MAXLABELS 127 /*!< 1-char labels. */ +#define KNOT_DNAME_MAXLABELLEN 63 /*!< 2^6 - 1 */ + +/*! + * \brief The longest textual dname representation. + * + * 3 x maximum_label + 1 x rest_label + 1 x zero_label + * Each dname label byte takes 4 characters (\\DDD). + * Each label takes 1 more byte for '.' character. + * + * KNOT_DNAME_TXT_MAXLEN = 3x(1 + 63x4) + 1x(1 + 61x4) + 1x(1 + 0) + */ +#define KNOT_DNAME_TXT_MAXLEN 1005 + +/*! + * \brief Address family numbers. + * + * https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xml + */ +typedef enum { + KNOT_ADDR_FAMILY_IPV4 = 1, /*!< IP version 4. */ + KNOT_ADDR_FAMILY_IPV6 = 2 /*!< IP version 6. */ +} knot_addr_family_t; + +/*! + * \brief DNS operation codes (OPCODEs). + * + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml + */ +typedef enum { + KNOT_OPCODE_QUERY = 0, /*!< Standard query. */ + KNOT_OPCODE_IQUERY = 1, /*!< Inverse query. */ + KNOT_OPCODE_STATUS = 2, /*!< Server status request. */ + KNOT_OPCODE_NOTIFY = 4, /*!< Notify message. */ + KNOT_OPCODE_UPDATE = 5 /*!< Dynamic update. */ +} knot_opcode_t; + +/*! + * \brief DNS reply codes (RCODEs). + * + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml + */ +typedef enum { + KNOT_RCODE_NOERROR = 0, /*!< No error. */ + KNOT_RCODE_FORMERR = 1, /*!< Format error. */ + KNOT_RCODE_SERVFAIL = 2, /*!< Server failure. */ + KNOT_RCODE_NXDOMAIN = 3, /*!< Non-existent domain. */ + KNOT_RCODE_NOTIMPL = 4, /*!< Not implemented. */ + KNOT_RCODE_REFUSED = 5, /*!< Refused. */ + KNOT_RCODE_YXDOMAIN = 6, /*!< Name should not exist. */ + KNOT_RCODE_YXRRSET = 7, /*!< RR set should not exist. */ + KNOT_RCODE_NXRRSET = 8, /*!< RR set does not exist. */ + KNOT_RCODE_NOTAUTH = 9, /*!< Server not authoritative. / Query not authorized. */ + KNOT_RCODE_NOTZONE = 10, /*!< Name is not inside zone. */ + KNOT_RCODE_BADVERS = 16, /*!< Bad OPT Version. */ + KNOT_RCODE_BADSIG = 16, /*!< (TSIG) Signature failure. */ + KNOT_RCODE_BADKEY = 17, /*!< (TSIG) Key is not supported. */ + KNOT_RCODE_BADTIME = 18, /*!< (TSIG) Signature out of time window. */ + KNOT_RCODE_BADMODE = 19, /*!< (TKEY) Bad mode. */ + KNOT_RCODE_BADNAME = 20, /*!< (TKEY) Duplicate key name. */ + KNOT_RCODE_BADALG = 21, /*!< (TKEY) Algorithm not supported. */ + KNOT_RCODE_BADTRUNC = 22, /*!< (TSIG) Bad truncation. */ + KNOT_RCODE_BADCOOKIE = 23 /*!< Bad/missing server cookie. */ +} knot_rcode_t; + +/*! + * \brief DNS packet section identifiers. + */ +typedef enum { + KNOT_ANSWER = 0, + KNOT_AUTHORITY = 1, + KNOT_ADDITIONAL = 2 +} knot_section_t; + +/*! + * \brief DS digest lengths. + */ +enum knot_ds_algorithm_len +{ + KNOT_DS_DIGEST_LEN_SHA1 = 20, /*!< RFC 3658 */ + KNOT_DS_DIGEST_LEN_SHA256 = 32, /*!< RFC 4509 */ + KNOT_DS_DIGEST_LEN_GOST = 32, /*!< RFC 5933 */ + KNOT_DS_DIGEST_LEN_SHA384 = 48 /*!< RFC 6605 */ +}; + +/*! + * \brief Constants for DNSSEC algorithm types. + * + * Source: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xml + */ +typedef enum { + KNOT_DS_ALG_SHA1 = 1, + KNOT_DS_ALG_SHA256 = 2, + KNOT_DS_ALG_GOST = 3, + KNOT_DS_ALG_SHA384 = 4 +} knot_ds_algorithm_t; + +/*! + * \brief DNSSEC algorithm numbers. + * + * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml + */ +typedef enum { + KNOT_DNSSEC_ALG_DELETE = 0, + KNOT_DNSSEC_ALG_RSAMD5 = 1, + KNOT_DNSSEC_ALG_DH = 2, + KNOT_DNSSEC_ALG_DSA = 3, + + KNOT_DNSSEC_ALG_RSASHA1 = 5, + KNOT_DNSSEC_ALG_DSA_NSEC3_SHA1 = 6, + KNOT_DNSSEC_ALG_RSASHA1_NSEC3_SHA1 = 7, + KNOT_DNSSEC_ALG_RSASHA256 = 8, + + KNOT_DNSSEC_ALG_RSASHA512 = 10, + + KNOT_DNSSEC_ALG_ECC_GOST = 12, + KNOT_DNSSEC_ALG_ECDSAP256SHA256 = 13, + KNOT_DNSSEC_ALG_ECDSAP384SHA384 = 14, + + KNOT_DNSSEC_ALG_ED25519 = 15, + KNOT_DNSSEC_ALG_ED448 = 16, + + KNOT_DNSSEC_ALG_INDIRECT = 252, + KNOT_DNSSEC_ALG_PRIVATEDNS = 253, + KNOT_DNSSEC_ALG_PRIVATEOID = 254 +} knot_dnssec_algorithm_t; + +/*! @} */ diff --git a/src/libknot/control/control.c b/src/libknot/control/control.c new file mode 100644 index 0000000..98ef0ab --- /dev/null +++ b/src/libknot/control/control.c @@ -0,0 +1,564 @@ +/* Copyright (C) 2020 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 <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/control/control.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "contrib/mempattern.h" +#include "contrib/net.h" +#include "contrib/sockaddr.h" +#include "contrib/string.h" +#include "contrib/ucw/mempool.h" +#include "contrib/wire_ctx.h" + +/*! Size of the input and output buffers. */ +#ifndef CTL_BUFF_SIZE +#define CTL_BUFF_SIZE (256 * 1024) +#endif + +/*! Listen backlog size. */ +#define LISTEN_BACKLOG 5 + +/*! Default socket operations timeout in milliseconds. */ +#define DEFAULT_TIMEOUT (5 * 1000) + +/*! The first data item code. */ +#define DATA_CODE_OFFSET 16 + +/*! Control context structure. */ +struct knot_ctl { + /*! Memory pool context. */ + knot_mm_t mm; + /*! Network operations timeout. */ + int timeout; + /*! Server listening socket. */ + int listen_sock; + /*! Remote server/client socket. */ + int sock; + + /*! The latter read data. */ + knot_ctl_data_t data; + + /*! Write wire context. */ + wire_ctx_t wire_out; + /*! Read wire context. */ + wire_ctx_t wire_in; + + /*! Write buffer. */ + uint8_t buff_out[CTL_BUFF_SIZE]; + /*! Read buffer. */ + uint8_t buff_in[CTL_BUFF_SIZE]; +}; + +static int type_to_code(knot_ctl_type_t type) +{ + switch (type) { + case KNOT_CTL_TYPE_END: return 0; + case KNOT_CTL_TYPE_DATA: return 1; + case KNOT_CTL_TYPE_EXTRA: return 2; + case KNOT_CTL_TYPE_BLOCK: return 3; + default: return -1; + } +} + +static int code_to_type(uint8_t code) +{ + switch (code) { + case 0: return KNOT_CTL_TYPE_END; + case 1: return KNOT_CTL_TYPE_DATA; + case 2: return KNOT_CTL_TYPE_EXTRA; + case 3: return KNOT_CTL_TYPE_BLOCK; + default: return -1; + } +} + +static bool is_data_type(knot_ctl_type_t type) +{ + switch (type) { + case KNOT_CTL_TYPE_DATA: + case KNOT_CTL_TYPE_EXTRA: + return true; + default: + return false; + } +} + +static int idx_to_code(knot_ctl_idx_t idx) +{ + if (idx >= KNOT_CTL_IDX__COUNT) { + return -1; + } + + return DATA_CODE_OFFSET + idx; +} + +static int code_to_idx(uint8_t code) +{ + if (code < DATA_CODE_OFFSET || + code >= DATA_CODE_OFFSET + KNOT_CTL_IDX__COUNT) { + return -1; + } + + return code - DATA_CODE_OFFSET; +} + +static void reset_buffers(knot_ctl_t *ctx) +{ + ctx->wire_out = wire_ctx_init(ctx->buff_out, CTL_BUFF_SIZE); + ctx->wire_in = wire_ctx_init(ctx->buff_in, 0); +} + +static void clean_data(knot_ctl_t *ctx) +{ + mp_flush(ctx->mm.ctx); + memzero(ctx->data, sizeof(ctx->data)); +} + +static void close_sock(int *sock) +{ + if (*sock < 0) { + return; + } + + close(*sock); + *sock = -1; +} + +_public_ +knot_ctl_t* knot_ctl_alloc(void) +{ + knot_ctl_t *ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } + + mm_ctx_mempool(&ctx->mm, MM_DEFAULT_BLKSIZE); + ctx->timeout = DEFAULT_TIMEOUT; + ctx->listen_sock = -1; + ctx->sock = -1; + + reset_buffers(ctx); + + return ctx; +} + +_public_ +void knot_ctl_free(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return; + } + + close_sock(&ctx->listen_sock); + close_sock(&ctx->sock); + + clean_data(ctx); + + mp_delete(ctx->mm.ctx); + + memzero(ctx, sizeof(*ctx)); + free(ctx); +} + +_public_ +void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms) +{ + if (ctx == NULL) { + return; + } + + ctx->timeout = (timeout_ms > 0) ? timeout_ms : -1; +} + +_public_ +int knot_ctl_bind(knot_ctl_t *ctx, const char *path) +{ + if (ctx == NULL || path == NULL) { + return KNOT_EINVAL; + } + + // Prepare socket address. + struct sockaddr_storage addr; + int ret = sockaddr_set(&addr, AF_UNIX, path, 0); + if (ret != KNOT_EOK) { + return ret; + } + + // Bind the socket. + ctx->listen_sock = net_bound_socket(SOCK_STREAM, &addr, 0); + if (ctx->listen_sock < 0) { + return ctx->listen_sock; + } + + // Start listening. + if (listen(ctx->listen_sock, LISTEN_BACKLOG) != 0) { + close_sock(&ctx->listen_sock); + return knot_map_errno(); + } + + return KNOT_EOK; +} + +_public_ +void knot_ctl_unbind(knot_ctl_t *ctx) +{ + if (ctx == NULL || ctx->listen_sock < 0) { + return; + } + + // Remove the control socket file. + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(ctx->listen_sock, (struct sockaddr *)&addr, &addr_len) == 0) { + char addr_str[SOCKADDR_STRLEN] = { 0 }; + if (sockaddr_tostr(addr_str, sizeof(addr_str), &addr) > 0) { + (void)unlink(addr_str); + } + } + + // Close the listening socket. + close_sock(&ctx->listen_sock); +} + +_public_ +int knot_ctl_accept(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + knot_ctl_close(ctx); + + // Control interface. + struct pollfd pfd = { .fd = ctx->listen_sock, .events = POLLIN }; + int ret = poll(&pfd, 1, -1); + if (ret <= 0) { + return knot_map_errno(); + } + + int client = net_accept(ctx->listen_sock, NULL); + if (client < 0) { + return client; + } + + ctx->sock = client; + + reset_buffers(ctx); + + return KNOT_EOK; +} + +_public_ +int knot_ctl_connect(knot_ctl_t *ctx, const char *path) +{ + if (ctx == NULL || path == NULL) { + return KNOT_EINVAL; + } + + // Prepare socket address. + struct sockaddr_storage addr; + int ret = sockaddr_set(&addr, AF_UNIX, path, 0); + if (ret != KNOT_EOK) { + return ret; + } + + // Connect to socket. + ctx->sock = net_connected_socket(SOCK_STREAM, &addr, NULL); + if (ctx->sock < 0) { + return ctx->sock; + } + + reset_buffers(ctx); + + return KNOT_EOK; +} + +_public_ +void knot_ctl_close(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return; + } + + close_sock(&ctx->sock); +} + +static int ensure_output(knot_ctl_t *ctx, uint16_t len) +{ + wire_ctx_t *w = &ctx->wire_out; + + // Check for enough available room in the output buffer. + size_t available = wire_ctx_available(w); + if (available >= len) { + return KNOT_EOK; + } + + // Flush the buffer. + int ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w), + ctx->timeout); + if (ret < 0) { + return ret; + } + + *w = wire_ctx_init(w->wire, CTL_BUFF_SIZE); + + return KNOT_EOK; +} + +static int send_item(knot_ctl_t *ctx, uint8_t code, const char *data, bool flush) +{ + wire_ctx_t *w = &ctx->wire_out; + + // Write the control block code. + int ret = ensure_output(ctx, sizeof(uint8_t)); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write_u8(w, code); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Control block data is optional. + if (data != NULL) { + // Get the data length. + size_t data_len = strlen(data); + if (data_len > UINT16_MAX) { + return KNOT_ERANGE; + } + + // Write the data length. + ret = ensure_output(ctx, sizeof(uint16_t)); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write_u16(w, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Write the data. + ret = ensure_output(ctx, data_len); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write(w, (uint8_t *)data, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + } + + // Send finalized buffer. + if (flush && wire_ctx_offset(w) > 0) { + ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w), + ctx->timeout); + if (ret < 0) { + return ret; + } + + *w = wire_ctx_init(w->wire, CTL_BUFF_SIZE); + } + + return KNOT_EOK; +} + +_public_ +int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + // Get the type code. + int code = type_to_code(type); + if (code == -1) { + return KNOT_EINVAL; + } + + // Send unit type. + int ret = send_item(ctx, code, NULL, !is_data_type(type)); + if (ret != KNOT_EOK) { + return ret; + } + + // Send unit data. + if (is_data_type(type) && data != NULL) { + // Send all non-empty data items. + for (knot_ctl_idx_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) { + const char *value = (*data)[i]; + if (value == NULL) { + continue; + } + + ret = send_item(ctx, idx_to_code(i), value, false); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + return KNOT_EOK; +} + +static int ensure_input(knot_ctl_t *ctx, uint16_t len) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Check for enough available room in the input buffer. + size_t available = wire_ctx_available(w); + if (available >= len) { + return KNOT_EOK; + } + + // Move unprocessed data to the beginning of the buffer. + memmove(w->wire, w->wire + wire_ctx_offset(w), available); + + // Receive enough data. + while (available < len) { + int ret = net_stream_recv(ctx->sock, w->wire + available, + CTL_BUFF_SIZE - available, + ctx->timeout); + if (ret < 0) { + return ret; + } + assert(ret > 0); + available += ret; + } + + ctx->wire_in = wire_ctx_init(w->wire, available); + + return KNOT_EOK; +} + +static int receive_item_code(knot_ctl_t *ctx, uint8_t *code) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Read the type. + int ret = ensure_input(ctx, sizeof(uint8_t)); + if (ret != KNOT_EOK) { + return ret; + } + *code = wire_ctx_read_u8(w); + if (w->error != KNOT_EOK) { + return w->error; + } + + return KNOT_EOK; +} + +static int receive_item_value(knot_ctl_t *ctx, char **value) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Read value length. + int ret = ensure_input(ctx, sizeof(uint16_t)); + if (ret != KNOT_EOK) { + return ret; + } + uint16_t data_len = wire_ctx_read_u16(w); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Read the value. + ret = ensure_input(ctx, data_len); + if (ret != KNOT_EOK) { + return ret; + } + *value = mm_alloc(&ctx->mm, data_len + 1); + if (*value == NULL) { + return KNOT_ENOMEM; + } + wire_ctx_read(w, *value, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + (*value)[data_len] = '\0'; + + return KNOT_EOK; +} + +_public_ +int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data) +{ + if (ctx == NULL || type == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_t *w = &ctx->wire_in; + + // Reset output variables. + *type = KNOT_CTL_TYPE_END; + clean_data(ctx); + + // Read data units until end of message. + bool have_type = false; + while (true) { + uint8_t code; + int ret = receive_item_code(ctx, &code); + if (ret != KNOT_EOK) { + return ret; + } + + // Process unit type. + int current_type = code_to_type(code); + if (current_type != -1) { + if (have_type) { + // Revert parsed type. + wire_ctx_skip(w, -sizeof(uint8_t)); + assert(w->error == KNOT_EOK); + break; + } + + // Set the unit type. + *type = current_type; + + if (is_data_type(current_type)) { + have_type = true; + continue; + } else { + break; + } + } + + // Check for data item code. + int idx = code_to_idx(code); + if (idx == -1) { + return KNOT_EINVAL; + } + + // Store the item data value. + ret = receive_item_value(ctx, (char **)&ctx->data[idx]); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Set the output data. + if (data != NULL) { + memcpy(*data, ctx->data, sizeof(*data)); + } + + return KNOT_EOK; +} diff --git a/src/libknot/control/control.h b/src/libknot/control/control.h new file mode 100644 index 0000000..47294f4 --- /dev/null +++ b/src/libknot/control/control.h @@ -0,0 +1,159 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief A server control interface. + * + * \addtogroup ctl + * @{ + */ + +#pragma once + +/*! Control data item indexes. */ +typedef enum { + KNOT_CTL_IDX_CMD = 0, /*!< Control command name. */ + KNOT_CTL_IDX_FLAGS, /*!< Control command flags. */ + KNOT_CTL_IDX_ERROR, /*!< Error message. */ + KNOT_CTL_IDX_SECTION, /*!< Configuration section name. */ + KNOT_CTL_IDX_ITEM, /*!< Configuration item name. */ + KNOT_CTL_IDX_ID, /*!< Congiguration item identifier. */ + KNOT_CTL_IDX_ZONE, /*!< Zone name. */ + KNOT_CTL_IDX_OWNER, /*!< Zone record owner */ + KNOT_CTL_IDX_TTL, /*!< Zone record TTL. */ + KNOT_CTL_IDX_TYPE, /*!< Zone record type name. */ + KNOT_CTL_IDX_DATA, /*!< Configuration item/zone record data. */ + KNOT_CTL_IDX_FILTER, /*!< An option or a filter for output data processing. */ + KNOT_CTL_IDX__COUNT, /*!< The number of data items. */ +} knot_ctl_idx_t; + +/*! Control unit types. */ +typedef enum { + KNOT_CTL_TYPE_END, /*!< End of message, cache flushed. */ + KNOT_CTL_TYPE_DATA, /*!< Data unit, cached. */ + KNOT_CTL_TYPE_EXTRA, /*!< Extra value data unit, cached. */ + KNOT_CTL_TYPE_BLOCK, /*!< End of data block, cache flushed. */ +} knot_ctl_type_t; + +/*! Control input/output string data. */ +typedef const char* knot_ctl_data_t[KNOT_CTL_IDX__COUNT]; + +/*! A control context. */ +struct knot_ctl; +typedef struct knot_ctl knot_ctl_t; + +/*! + * Allocates a control context. + * + * \return Control context. + */ +knot_ctl_t* knot_ctl_alloc(void); + +/*! + * Deallocates a control context. + * + * \param[in] ctx Control context. + */ +void knot_ctl_free(knot_ctl_t *ctx); + +/*! + * Sets the timeout for socket operations. + * + * Default value is 5 seconds. + * + * \param[in] ctx Control context. + * \param[in] timeout_ms Timeout in milliseconds (0 for infinity). + */ +void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms); + +/*! + * Binds a specified UNIX socket path. + * + * \note Server operation. + * + * \param[in] ctx Control context. + * \param[in] path Control UNIX socket path. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_bind(knot_ctl_t *ctx, const char *path); + +/*! + * Unbinds a control socket. + * + * \note Server operation. + * + * \param[in] ctx Control context. + */ +void knot_ctl_unbind(knot_ctl_t *ctx); + +/*! + * Connects to a specified UNIX socket path. + * + * \note Client operation. + * + * \param[in] ctx Control context. + * \param[in] path Control UNIX socket path. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_connect(knot_ctl_t *ctx, const char *path); + +/*! + * Waits for an incoming connection. + * + * \note Server operation. + * + * \param[in] ctx Control context. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_accept(knot_ctl_t *ctx); + +/*! + * Closes the remote connections. + * + * \note Applies to both server and client. + * + * \param[in] ctx Control context. + */ +void knot_ctl_close(knot_ctl_t *ctx); + +/*! + * Sends one control unit. + * + * \param[in] ctx Control context. + * \param[in] type Unit type to send. + * \param[in] data Data unit to send (optional, ignored if non-data type). + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data); + +/*! + * Receives one control unit. + * + * \param[in] ctx Control context. + * \param[out] type Received unit type. + * \param[out] data Received data unit (optional). + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data); + +/*! @} */ diff --git a/src/libknot/cookies.c b/src/libknot/cookies.c new file mode 100644 index 0000000..e7b3354 --- /dev/null +++ b/src/libknot/cookies.c @@ -0,0 +1,170 @@ +/* Copyright (C) 2019 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 <stdbool.h> + +#include "libknot/attribute.h" +#include "libknot/cookies.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "contrib/string.h" +#include "contrib/sockaddr.h" +#include "contrib/openbsd/siphash.h" + +_public_ +int knot_edns_cookie_client_generate(knot_edns_cookie_t *out, + const knot_edns_cookie_params_t *params) +{ + if (out == NULL || params == NULL || params->server_addr == NULL) { + return KNOT_EINVAL; + } + + SIPHASH_CTX ctx; + assert(sizeof(params->secret) == sizeof(SIPHASH_KEY)); + SipHash24_Init(&ctx, (const SIPHASH_KEY *)params->secret); + + size_t addr_len = 0; + void *addr = sockaddr_raw(params->server_addr, &addr_len); + assert(addr); + SipHash24_Update(&ctx, addr, addr_len); + + uint64_t hash = SipHash24_End(&ctx); + memcpy(out->data, &hash, sizeof(hash)); + out->len = sizeof(hash); + + return KNOT_EOK; +} + +_public_ +int knot_edns_cookie_client_check(const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params) +{ + if (cc == NULL || cc->len != KNOT_EDNS_COOKIE_CLNT_SIZE) { + return KNOT_EINVAL; + } + + knot_edns_cookie_t ref; + int ret = knot_edns_cookie_client_generate(&ref, params); + if (ret != KNOT_EOK) { + return ret; + } + assert(ref.len == KNOT_EDNS_COOKIE_CLNT_SIZE); + + ret = const_time_memcmp(cc->data, ref.data, KNOT_EDNS_COOKIE_CLNT_SIZE); + if (ret != 0) { + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +static int cookie_server_generate(knot_edns_cookie_t *out, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params) +{ + assert(out && params); + + if (cc == NULL || cc->len != KNOT_EDNS_COOKIE_CLNT_SIZE || + params->client_addr == NULL) { + return KNOT_EINVAL; + } else if (out->data[0] != KNOT_EDNS_COOKIE_VERSION) { + return KNOT_ENOTSUP; + } + + SIPHASH_CTX ctx; + assert(sizeof(params->secret) == sizeof(SIPHASH_KEY)); + SipHash24_Init(&ctx, (const SIPHASH_KEY *)params->secret); + + SipHash24_Update(&ctx, cc->data, cc->len); + SipHash24_Update(&ctx, out->data, out->len); + + size_t addr_len = 0; + void *addr = sockaddr_raw(params->client_addr, &addr_len); + assert(addr); + SipHash24_Update(&ctx, addr, addr_len); + + uint64_t hash = SipHash24_End(&ctx); + memcpy(out->data + out->len, &hash, sizeof(hash)); + out->len += sizeof(hash); + + return KNOT_EOK; + +} + +_public_ +int knot_edns_cookie_server_generate(knot_edns_cookie_t *out, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params) +{ + if (out == NULL || params == NULL) { + return KNOT_EINVAL; + } + + out->data[0] = params->version; + out->data[1] = 0; /* reserved */ + out->data[2] = 0; /* reserved */ + out->data[3] = 0; /* reserved */ + out->len = 4; + + uint32_t now = htobe32(params->timestamp); + memcpy(&out->data[out->len], &now, sizeof(now)); + out->len += sizeof(now); + + return cookie_server_generate(out, cc, params); +} + +_public_ +int knot_edns_cookie_server_check(const knot_edns_cookie_t *sc, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params) +{ + if (sc == NULL || sc->len < KNOT_EDNS_COOKIE_SRVR_MIN_SIZE || params == NULL) { + return KNOT_EINVAL; + } + + uint32_t cookie_time; + memcpy(&cookie_time, &sc->data[4], sizeof(cookie_time)); + cookie_time = be32toh(cookie_time); + + uint32_t min_time = params->timestamp - params->lifetime_before; + uint32_t max_time = params->timestamp + params->lifetime_after; + if (cookie_time < min_time || cookie_time > max_time) { + return KNOT_ERANGE; + } + + const int fixed_len = 8; + knot_edns_cookie_t ref; + memcpy(ref.data, sc->data, fixed_len); + ref.len = fixed_len; + + int ret = cookie_server_generate(&ref, cc, params); + if (ret != KNOT_EOK) { + return ret; + } + + if (sc->len != ref.len) { + return KNOT_EINVAL; + } + + ret = const_time_memcmp(sc->data + fixed_len, ref.data + fixed_len, + sc->len - fixed_len); + if (ret != 0) { + return KNOT_EINVAL; + } + + return KNOT_EOK; +} diff --git a/src/libknot/cookies.h b/src/libknot/cookies.h new file mode 100644 index 0000000..b39ca93 --- /dev/null +++ b/src/libknot/cookies.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \brief DNS cookies processing. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <sys/socket.h> + +#include "libknot/rrtype/opt.h" + +#define KNOT_EDNS_COOKIE_SECRET_SIZE 16 + +/*! + * \brief DNS Cookie parameters needed to generate/check the cookie value. + * + * \note Client address is not used for the client cookie generation/check. + * \note Server address is not used for the server cookie generation/check. + */ +typedef struct { + uint8_t version; /*!< Server cookie version to generate. */ + uint32_t timestamp; /*!< [s] Server cookie generate or check time. */ + uint32_t lifetime_before; /*!< [s] Server cookie lifetime in the past. */ + uint32_t lifetime_after; /*!< [s] Server cookie lifetime in the future. */ + const struct sockaddr_storage *client_addr; /*!< Client socket address. */ + const struct sockaddr_storage *server_addr; /*!< Server socket address. */ + uint8_t secret[KNOT_EDNS_COOKIE_SECRET_SIZE]; /*!< Cookie secret data. */ +} knot_edns_cookie_params_t; + +/*! + * \brief Generate a client cookie using given parameters. + * + * \param out Generated client cookie. + * \param params Client cookie parameters. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int knot_edns_cookie_client_generate(knot_edns_cookie_t *out, + const knot_edns_cookie_params_t *params); + +/*! + * \brief Check whether client cookie was generated using given parameters. + * + * \param cc Client cookie that should be checked. + * \param params Client cookie parameters. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int knot_edns_cookie_client_check(const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params); + +/*! + * \brief Generate a server cookie using given parameters. + * + * \param out Generated server cookie. + * \param cc Client cookie parameter. + * \param params Server cookie parameters. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int knot_edns_cookie_server_generate(knot_edns_cookie_t *out, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params); + +/*! + * \brief Check whether server cookie was generated using given parameters. + * + * \param sc Server cookie that should be checked. + * \param cc Client cookie parameter. + * \param params Server cookie parameters. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int knot_edns_cookie_server_check(const knot_edns_cookie_t *sc, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_params_t *params); + +/*! @} */ diff --git a/src/libknot/db/db.h b/src/libknot/db/db.h new file mode 100644 index 0000000..044a2b9 --- /dev/null +++ b/src/libknot/db/db.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief Structures for binary data handling. + * + * \addtogroup db + * @{ + */ + +#pragma once + +#include "libknot/mm_ctx.h" + +enum { + /* Database flags */ + + KNOT_DB_RDONLY = 1 << 0, /*!< Read only. */ + KNOT_DB_SORTED = 1 << 1, /*!< Sorted output. */ + + /* Operations */ + + KNOT_DB_NOOP = 1 << 2, /*!< No operation. */ + KNOT_DB_FIRST = 1 << 3, /*!< First entry. */ + KNOT_DB_LAST = 1 << 4, /*!< Last entry. */ + KNOT_DB_NEXT = 1 << 5, /*!< Next entry. */ + KNOT_DB_PREV = 1 << 6, /*!< Previous entry. */ + KNOT_DB_LEQ = 1 << 7, /*!< Lesser or equal. */ + KNOT_DB_GEQ = 1 << 8 /*!< Greater or equal. */ +}; + +typedef void knot_db_t; +typedef void knot_db_iter_t; + +typedef struct knot_db_val { + void *data; + size_t len; +} knot_db_val_t; + +typedef struct knot_db_txn { + knot_db_t *db; + void *txn; +} knot_db_txn_t; + +typedef struct knot_db_api { + const char *name; + + /* Context operations */ + + int (*init)(knot_db_t **db, knot_mm_t *mm, void *opts); + void (*deinit)(knot_db_t *db); + + /* Transactions */ + + int (*txn_begin)(knot_db_t *db, knot_db_txn_t *txn, unsigned flags); + int (*txn_commit)(knot_db_txn_t *txn); + void (*txn_abort)(knot_db_txn_t *txn); + + /* Data access */ + + int (*count)(knot_db_txn_t *txn); + int (*clear)(knot_db_txn_t *txn); + int (*find)(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags); + int (*insert)(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags); + int (*del)(knot_db_txn_t *txn, knot_db_val_t *key); + + /* Iteration */ + + knot_db_iter_t *(*iter_begin)(knot_db_txn_t *txn, unsigned flags); + knot_db_iter_t *(*iter_seek)(knot_db_iter_t *iter, knot_db_val_t *key, unsigned flags); + knot_db_iter_t *(*iter_next)(knot_db_iter_t *iter); + int (*iter_key)(knot_db_iter_t *iter, knot_db_val_t *key); + int (*iter_val)(knot_db_iter_t *iter, knot_db_val_t *val); + void (*iter_finish)(knot_db_iter_t *iter); +} knot_db_api_t; + +/*! @} */ diff --git a/src/libknot/db/db_lmdb.c b/src/libknot/db/db_lmdb.c new file mode 100644 index 0000000..0ac1eaf --- /dev/null +++ b/src/libknot/db/db_lmdb.c @@ -0,0 +1,581 @@ +/* Copyright (C) 2019 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 <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/db/db_lmdb.h" +#include "contrib/mempattern.h" + +#include <lmdb.h> + +/* Defines */ +#define LMDB_DIR_MODE 0770 +#define LMDB_FILE_MODE 0660 + +_public_ const unsigned KNOT_DB_LMDB_NOTLS = MDB_NOTLS; +_public_ const unsigned KNOT_DB_LMDB_RDONLY = MDB_RDONLY; +_public_ const unsigned KNOT_DB_LMDB_INTEGERKEY = MDB_INTEGERKEY; +_public_ const unsigned KNOT_DB_LMDB_NOSYNC = MDB_NOSYNC; +_public_ const unsigned KNOT_DB_LMDB_WRITEMAP = MDB_WRITEMAP; +_public_ const unsigned KNOT_DB_LMDB_MAPASYNC = MDB_MAPASYNC; +_public_ const unsigned KNOT_DB_LMDB_DUPSORT = MDB_DUPSORT; + +struct lmdb_env +{ + bool shared; + MDB_dbi dbi; + MDB_env *env; + knot_mm_t *pool; +}; + +/*! + * \brief Convert error code returned by LMDB to Knot DNS error code. + * + * LMDB defines own error codes but uses additional ones from libc: + * - LMDB errors do not conflict with Knot DNS ones. + * - Significant LMDB errors are mapped to Knot DNS ones. + * - Standard errors are converted to negative value to match Knot DNS mapping. + */ +static int lmdb_error_to_knot(int error) +{ + if (error == MDB_SUCCESS) { + return KNOT_EOK; + } + + if (error == MDB_NOTFOUND) { + return KNOT_ENOENT; + } + + if (error == MDB_TXN_FULL) { + return KNOT_ELIMIT; + } + + if (error == MDB_MAP_FULL || error == ENOSPC) { + return KNOT_ESPACE; + } + + return -abs(error); +} + +static int create_env_dir(const char *path) +{ + int r = mkdir(path, LMDB_DIR_MODE); + if (r == -1 && errno != EEXIST) { + return lmdb_error_to_knot(errno); + } + + return KNOT_EOK; +} + +/*! \brief Set the environment map size. + * \note This also sets the maximum database size, see mdb_env_set_mapsize + */ +static int set_mapsize(MDB_env *env, size_t map_size) +{ + long page_size = sysconf(_SC_PAGESIZE); + if (page_size <= 0) { + return KNOT_ERROR; + } + + /* Round to page size. */ + map_size = (map_size / page_size) * page_size; + int ret = mdb_env_set_mapsize(env, map_size); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +/*! \brief Close the database. */ +static void dbase_close(struct lmdb_env *env) +{ + mdb_dbi_close(env->env, env->dbi); + if (!env->shared) { + mdb_env_close(env->env); + } +} + +/*! \brief Open database environment. */ +static int dbase_open_env(struct lmdb_env *env, struct knot_db_lmdb_opts *opts) +{ + MDB_env *mdb_env = NULL; + int ret = mdb_env_create(&mdb_env); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + ret = create_env_dir(opts->path); + if (ret != KNOT_EOK) { + mdb_env_close(mdb_env); + return ret; + } + + ret = set_mapsize(mdb_env, opts->mapsize); + if (ret != KNOT_EOK) { + mdb_env_close(mdb_env); + return ret; + } + + ret = mdb_env_set_maxdbs(mdb_env, opts->maxdbs); + if (ret != MDB_SUCCESS) { + mdb_env_close(mdb_env); + return lmdb_error_to_knot(ret); + } + + ret = mdb_env_set_maxreaders(mdb_env, opts->maxreaders); + if (ret != MDB_SUCCESS) { + mdb_env_close(mdb_env); + return lmdb_error_to_knot(ret); + } + +#ifdef __OpenBSD__ + /* + * Enforce that MDB_WRITEMAP is set. + * + * MDB assumes a unified buffer cache. + * + * See https://www.openldap.org/pub/hyc/mdm-paper.pdf section 3.1, + * references 17, 18, and 19. + * + * From Howard Chu: "This requirement can be relaxed in the + * current version of the library. If you create the environment + * with the MDB_WRITEMAP option then all reads and writes are + * performed using mmap, so the file buffer cache is irrelevant. + * Of course then you lose the protection that the read-only + * map offers." + */ + opts->flags.env |= MDB_WRITEMAP; +#endif + + ret = mdb_env_open(mdb_env, opts->path, opts->flags.env, LMDB_FILE_MODE); + if (ret != MDB_SUCCESS) { + mdb_env_close(mdb_env); + return lmdb_error_to_knot(ret); + } + + /* Keep the environment pointer. */ + env->env = mdb_env; + + return KNOT_EOK; +} + +static int dbase_open(struct lmdb_env *env, struct knot_db_lmdb_opts *opts) +{ + unsigned flags = 0; + if (opts->flags.env & KNOT_DB_LMDB_RDONLY) { + flags = MDB_RDONLY; + } + + /* Open the database. */ + MDB_txn *txn = NULL; + int ret = mdb_txn_begin(env->env, NULL, flags, &txn); + if (ret != MDB_SUCCESS) { + mdb_env_close(env->env); + return lmdb_error_to_knot(ret); + } + + ret = mdb_dbi_open(txn, opts->dbname, opts->flags.db | MDB_CREATE, &env->dbi); + if (ret != MDB_SUCCESS) { + mdb_txn_abort(txn); + mdb_env_close(env->env); + return lmdb_error_to_knot(ret); + } + + ret = mdb_txn_commit(txn); + if (ret != MDB_SUCCESS) { + mdb_env_close(env->env); + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +static int init(knot_db_t **db_ptr, knot_mm_t *mm, void *arg) +{ + if (db_ptr == NULL || arg == NULL) { + return KNOT_EINVAL; + } + + struct lmdb_env *env = mm_alloc(mm, sizeof(struct lmdb_env)); + if (env == NULL) { + return KNOT_ENOMEM; + } + + memset(env, 0, sizeof(struct lmdb_env)); + env->pool = mm; + + /* Open new environment. */ + struct lmdb_env *old_env = *db_ptr; + if (old_env == NULL) { + int ret = dbase_open_env(env, (struct knot_db_lmdb_opts *)arg); + if (ret != KNOT_EOK) { + mm_free(mm, env); + return ret; + } + } else { + /* Shared environment, this instance just owns the DBI. */ + env->env = old_env->env; + env->shared = true; + } + + /* Open the database. */ + int ret = dbase_open(env, (struct knot_db_lmdb_opts *)arg); + if (ret != KNOT_EOK) { + mm_free(mm, env); + return ret; + } + + /* Store the new environment. */ + *db_ptr = env; + + return KNOT_EOK; +} + +static void deinit(knot_db_t *db) +{ + if (db) { + struct lmdb_env *env = db; + + dbase_close(env); + mm_free(env->pool, env); + } +} + +_public_ +int knot_db_lmdb_txn_begin(knot_db_t *db, knot_db_txn_t *txn, knot_db_txn_t *parent, + unsigned flags) +{ + txn->db = db; + txn->txn = NULL; + + unsigned txn_flags = 0; + if (flags & KNOT_DB_RDONLY) { + txn_flags |= MDB_RDONLY; + } + + MDB_txn *parent_txn = (parent != NULL) ? (MDB_txn *)parent->txn : NULL; + + struct lmdb_env *env = db; + int ret = mdb_txn_begin(env->env, parent_txn, txn_flags, (MDB_txn **)&txn->txn); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +static int txn_begin(knot_db_t *db, knot_db_txn_t *txn, unsigned flags) +{ + return knot_db_lmdb_txn_begin(db, txn, NULL, flags); +} + +static int txn_commit(knot_db_txn_t *txn) +{ + int ret = mdb_txn_commit((MDB_txn *)txn->txn); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +static void txn_abort(knot_db_txn_t *txn) +{ + mdb_txn_abort((MDB_txn *)txn->txn); +} + +static int count(knot_db_txn_t *txn) +{ + struct lmdb_env *env = txn->db; + + MDB_stat stat; + int ret = mdb_stat(txn->txn, env->dbi, &stat); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return stat.ms_entries; +} + +static int clear(knot_db_txn_t *txn) +{ + struct lmdb_env *env = txn->db; + + int ret = mdb_drop(txn->txn, env->dbi, 0); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +static knot_db_iter_t *iter_set(knot_db_iter_t *iter, knot_db_val_t *key, unsigned flags) +{ + MDB_cursor *cursor = iter; + + MDB_cursor_op op = MDB_SET; + switch(flags) { + case KNOT_DB_NOOP: return cursor; + case KNOT_DB_FIRST: op = MDB_FIRST; break; + case KNOT_DB_LAST: op = MDB_LAST; break; + case KNOT_DB_NEXT: op = MDB_NEXT; break; + case KNOT_DB_PREV: op = MDB_PREV; break; + case KNOT_DB_LEQ: + case KNOT_DB_GEQ: op = MDB_SET_RANGE; break; + default: break; + } + + MDB_val db_key = { 0, NULL }; + if (key) { + db_key.mv_data = key->data; + db_key.mv_size = key->len; + } + MDB_val unused_key = { 0, NULL }, unused_val = { 0, NULL }; + + int ret = mdb_cursor_get(cursor, key ? &db_key : &unused_key, &unused_val, op); + + /* LEQ is not supported in LMDB, workaround using GEQ. */ + if (flags == KNOT_DB_LEQ && key) { + /* Searched key is after the last key. */ + if (ret != MDB_SUCCESS) { + return iter_set(iter, NULL, KNOT_DB_LAST); + } + /* If the searched key != matched, get previous. */ + if ((key->len != db_key.mv_size) || + (memcmp(key->data, db_key.mv_data, key->len) != 0)) { + return iter_set(iter, NULL, KNOT_DB_PREV); + } + } + + if (ret != MDB_SUCCESS) { + mdb_cursor_close(cursor); + return NULL; + } + + return cursor; +} + +static knot_db_iter_t *iter_begin(knot_db_txn_t *txn, unsigned flags) +{ + struct lmdb_env *env = txn->db; + MDB_cursor *cursor = NULL; + + int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor); + if (ret != MDB_SUCCESS) { + return NULL; + } + + /* Clear sorted flag, as it's always sorted. */ + flags &= ~KNOT_DB_SORTED; + + return iter_set(cursor, NULL, (flags == 0) ? KNOT_DB_FIRST : flags); +} + +static knot_db_iter_t *iter_next(knot_db_iter_t *iter) +{ + return iter_set(iter, NULL, KNOT_DB_NEXT); +} + +_public_ +int knot_db_lmdb_iter_del(knot_db_iter_t *iter) +{ + MDB_cursor *cursor = iter; + + int ret = mdb_cursor_del(cursor, 0); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +static int iter_key(knot_db_iter_t *iter, knot_db_val_t *key) +{ + MDB_cursor *cursor = iter; + + MDB_val mdb_key, mdb_val; + int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + key->data = mdb_key.mv_data; + key->len = mdb_key.mv_size; + return KNOT_EOK; +} + +static int iter_val(knot_db_iter_t *iter, knot_db_val_t *val) +{ + MDB_cursor *cursor = iter; + + MDB_val mdb_key, mdb_val; + int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + val->data = mdb_val.mv_data; + val->len = mdb_val.mv_size; + return KNOT_EOK; +} + +static void iter_finish(knot_db_iter_t *iter) +{ + if (iter == NULL) { + return; + } + + MDB_cursor *cursor = iter; + mdb_cursor_close(cursor); +} + +static int find(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags) +{ + knot_db_iter_t *iter = iter_begin(txn, KNOT_DB_NOOP); + if (iter == NULL) { + return KNOT_ERROR; + } + + int ret = KNOT_EOK; + if (iter_set(iter, key, flags) == NULL) { + return KNOT_ENOENT; + } else { + ret = iter_val(iter, val); + } + + iter_finish(iter); + return ret; +} + +static int insert(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags) +{ + struct lmdb_env *env = txn->db; + + MDB_val db_key = { key->len, key->data }; + MDB_val data = { val->len, val->data }; + + /* Reserve if only size is declared. */ + unsigned mdb_flags = 0; + if (val->len > 0 && val->data == NULL) { + mdb_flags |= MDB_RESERVE; + } + + int ret = mdb_put(txn->txn, env->dbi, &db_key, &data, mdb_flags); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + /* Update the result. */ + val->data = data.mv_data; + val->len = data.mv_size; + + return KNOT_EOK; +} + +static int del(knot_db_txn_t *txn, knot_db_val_t *key) +{ + struct lmdb_env *env = txn->db; + MDB_val db_key = { key->len, key->data }; + MDB_val data = { 0, NULL }; + + int ret = mdb_del(txn->txn, env->dbi, &db_key, &data); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +_public_ +int knot_db_lmdb_del_exact(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val) +{ + struct lmdb_env *env = txn->db; + MDB_val db_key = { key->len, key->data }; + MDB_val data = { val->len, val->data }; + + int ret = mdb_del(txn->txn, env->dbi, &db_key, &data); + if (ret != MDB_SUCCESS) { + return lmdb_error_to_knot(ret); + } + + return KNOT_EOK; +} + +_public_ +size_t knot_db_lmdb_get_mapsize(knot_db_t *db) +{ + struct lmdb_env *env = db; + MDB_envinfo info; + if (mdb_env_info(env->env, &info) != MDB_SUCCESS) { + return 0; + } + + return info.me_mapsize; +} + +// you should SUM all the usages of DBs sharing one mapsize +_public_ +size_t knot_db_lmdb_get_usage(knot_db_t *db) +{ + struct lmdb_env *env = db; + knot_db_txn_t txn; + knot_db_lmdb_txn_begin(db, &txn, NULL, KNOT_DB_RDONLY); + MDB_stat st; + if (mdb_stat(txn.txn, env->dbi, &st) != MDB_SUCCESS) { + txn_abort(&txn); + return 0; + } + txn_abort(&txn); + + size_t pgs_used = st.ms_branch_pages + st.ms_leaf_pages + st.ms_overflow_pages; + + return (pgs_used * st.ms_psize); +} + +_public_ +const char *knot_db_lmdb_get_path(knot_db_t *db) +{ + struct lmdb_env *env = db; + + static const char *path; + if (mdb_env_get_path(env->env, &path) == 0) { + return path; + } else { + return ""; + } +} + +_public_ +const knot_db_api_t *knot_db_lmdb_api(void) +{ + static const knot_db_api_t api = { + "lmdb", + init, deinit, + txn_begin, txn_commit, txn_abort, + count, clear, find, insert, del, + iter_begin, iter_set, iter_next, iter_key, iter_val, iter_finish + }; + + return &api; +} diff --git a/src/libknot/db/db_lmdb.h b/src/libknot/db/db_lmdb.h new file mode 100644 index 0000000..ef8e664 --- /dev/null +++ b/src/libknot/db/db_lmdb.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \addtogroup db + * @{ + */ + +#pragma once + +#include "libknot/db/db.h" + +/* Defines. */ +#define KNOT_DB_LMDB_MAPSIZE (100 * 1024 * 1024) + +/* LMDB specific flags. */ +extern const unsigned KNOT_DB_LMDB_NOTLS; +extern const unsigned KNOT_DB_LMDB_RDONLY; +extern const unsigned KNOT_DB_LMDB_INTEGERKEY; +extern const unsigned KNOT_DB_LMDB_NOSYNC; +extern const unsigned KNOT_DB_LMDB_WRITEMAP; +extern const unsigned KNOT_DB_LMDB_MAPASYNC; +extern const unsigned KNOT_DB_LMDB_DUPSORT; + +/* Native options. */ +struct knot_db_lmdb_opts { + const char *path; /*!< Database environment path. */ + const char *dbname; /*!< Database name (or NULL). */ + size_t mapsize; /*!< Environment map size. */ + unsigned maxdbs; /*!< Maximum number of databases in the env. */ + unsigned maxreaders; /*!< Maximum number of concurrent readers */ + struct { + unsigned env; /*!< Environment flags. */ + unsigned db; /*!< Database flags. */ + } flags; +}; + +/* Default options. */ +#define KNOT_DB_LMDB_OPTS_INITIALIZER { \ + NULL, NULL, \ + KNOT_DB_LMDB_MAPSIZE, \ + 0, \ + 126, /* = contrib/lmdb/mdb.c DEFAULT_READERS */ \ + { 0, 0 } \ +} + +const knot_db_api_t *knot_db_lmdb_api(void); + +/* LMDB specific operations. */ +int knot_db_lmdb_del_exact(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val); +int knot_db_lmdb_txn_begin(knot_db_t *db, knot_db_txn_t *txn, knot_db_txn_t *parent, + unsigned flags); +int knot_db_lmdb_iter_del(knot_db_iter_t *iter); +size_t knot_db_lmdb_get_mapsize(knot_db_t *db); +size_t knot_db_lmdb_get_usage(knot_db_t *db); +const char *knot_db_lmdb_get_path(knot_db_t *db); + +/*! @} */ diff --git a/src/libknot/db/db_trie.c b/src/libknot/db/db_trie.c new file mode 100644 index 0000000..c83cd56 --- /dev/null +++ b/src/libknot/db/db_trie.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2016 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 "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/db/db_trie.h" +#include "contrib/qp-trie/trie.h" +#include "contrib/macros.h" +#include "contrib/mempattern.h" + +static int init(knot_db_t **db, knot_mm_t *mm, void *arg) +{ + if (db == NULL || arg == NULL) { + return KNOT_EINVAL; + } + + struct knot_db_trie_opts *opts = arg; + UNUSED(opts); + trie_t *trie = trie_create(mm); + if (!trie) { + return KNOT_ENOMEM; + } + + *db = trie; + + return KNOT_EOK; +} + +static void deinit(knot_db_t *db) +{ + trie_free((trie_t *)db); +} + +static int txn_begin(knot_db_t *db, knot_db_txn_t *txn, unsigned flags) +{ + txn->txn = (void *)(size_t)flags; + txn->db = db; + return KNOT_EOK; /* N/A */ +} + +static int txn_commit(knot_db_txn_t *txn) +{ + return KNOT_EOK; +} + +static void txn_abort(knot_db_txn_t *txn) +{ +} + +static int count(knot_db_txn_t *txn) +{ + return trie_weight((trie_t *)txn->db); +} + +static int clear(knot_db_txn_t *txn) +{ + trie_clear((trie_t *)txn->db); + + return KNOT_EOK; +} + +static int find(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags) +{ + trie_val_t *ret = trie_get_try((trie_t *)txn->db, key->data, key->len); + if (ret == NULL) { + return KNOT_ENOENT; + } + + val->data = *ret; + val->len = sizeof(trie_val_t); /* Trie doesn't support storing length. */ + return KNOT_EOK; +} + +static int insert(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags) +{ + /* No flags supported. */ + if (flags != 0) { + return KNOT_ENOTSUP; + } + + trie_val_t *ret = trie_get_ins((trie_t *)txn->db, key->data, key->len); + if (ret == NULL) { + return KNOT_ENOMEM; + } + + *ret = val->data; + return KNOT_EOK; +} + +static int del(knot_db_txn_t *txn, knot_db_val_t *key) +{ + return trie_del((trie_t *)txn->db, key->data, key->len, NULL); +} + +static knot_db_iter_t *iter_begin(knot_db_txn_t *txn, unsigned flags) +{ + flags &= ~KNOT_DB_SORTED; + + /* No operations other than begin are supported right now. */ + if (flags != 0) { + return NULL; + } + + return trie_it_begin((trie_t *)txn->db); +} + +static knot_db_iter_t *iter_seek(knot_db_iter_t *iter, knot_db_val_t *key, unsigned flags) +{ + assert(0); + return NULL; /* ENOTSUP */ +} + +static knot_db_iter_t *iter_next(knot_db_iter_t *iter) +{ + trie_it_next((trie_it_t *)iter); + if (trie_it_finished((trie_it_t *)iter)) { + trie_it_free((trie_it_t *)iter); + return NULL; + } + + return iter; +} + +static int iter_key(knot_db_iter_t *iter, knot_db_val_t *val) +{ + val->data = (void *)trie_it_key((trie_it_t *)iter, &val->len); + if (val->data == NULL) { + return KNOT_ENOENT; + } + + return KNOT_EOK; +} + +static int iter_val(knot_db_iter_t *iter, knot_db_val_t *val) +{ + trie_val_t *ret = trie_it_val((trie_it_t *)iter); + if (ret == NULL) { + return KNOT_ENOENT; + } + + val->data = *ret; + val->len = sizeof(trie_val_t); + return KNOT_EOK; +} + +static void iter_finish(knot_db_iter_t *iter) +{ + trie_it_free((trie_it_t *)iter); +} + +_public_ +const knot_db_api_t *knot_db_trie_api(void) +{ + static const knot_db_api_t api = { + "trie", + init, deinit, + txn_begin, txn_commit, txn_abort, + count, clear, find, insert, del, + iter_begin, iter_seek, iter_next, iter_key, iter_val, iter_finish + }; + + return &api; +} diff --git a/src/libknot/db/db_trie.h b/src/libknot/db/db_trie.h new file mode 100644 index 0000000..fee4722 --- /dev/null +++ b/src/libknot/db/db_trie.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \addtogroup db + * @{ + */ + +#pragma once + +#include "libknot/db/db.h" + +/* Native options. */ +struct knot_db_trie_opts { + unsigned unused; +}; + +/* Default options. */ +#define KNOT_DB_TRIE_OPTS_INITIALIZER { \ + 0 \ +} + +const knot_db_api_t *knot_db_trie_api(void); + +/*! @} */ diff --git a/src/libknot/descriptor.c b/src/libknot/descriptor.c new file mode 100644 index 0000000..694ec5c --- /dev/null +++ b/src/libknot/descriptor.c @@ -0,0 +1,423 @@ +/* Copyright (C) 2019 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 <stdio.h> +#include <stdlib.h> +#include <strings.h> + +#include "libknot/attribute.h" +#include "libknot/descriptor.h" + +/*! + * \brief Table with DNS classes. + */ +static const char* dns_classes[] = { + [KNOT_CLASS_IN] = "IN", + [KNOT_CLASS_CH] = "CH", + [KNOT_CLASS_NONE] = "NONE", + [KNOT_CLASS_ANY] = "ANY" +}; + +/*! + * \brief RR type descriptors. + */ +static const knot_rdata_descriptor_t rdata_descriptors[] = { + [0] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, NULL }, + [KNOT_RRTYPE_A] = { { 4, KNOT_RDATA_WF_END }, "A" }, + [KNOT_RRTYPE_NS] = { { KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "NS" }, + [KNOT_RRTYPE_CNAME] = { { KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "CNAME" }, + [KNOT_RRTYPE_SOA] = { { KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + 20, KNOT_RDATA_WF_END }, "SOA" }, + [KNOT_RRTYPE_NULL] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "NULL" }, + [KNOT_RRTYPE_PTR] = { { KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "PTR" }, + [KNOT_RRTYPE_HINFO] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "HINFO" }, + [KNOT_RRTYPE_MINFO] = { { KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MINFO" }, + [KNOT_RRTYPE_MX] = { { 2, KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MX" }, + [KNOT_RRTYPE_TXT] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "TXT" }, + [KNOT_RRTYPE_RP] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "RP" }, + [KNOT_RRTYPE_AFSDB] = { { 2, KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "AFSDB" }, + [KNOT_RRTYPE_RT] = { { 2, KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "RT" }, + [KNOT_RRTYPE_SIG] = { { 18, KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "SIG" }, + [KNOT_RRTYPE_KEY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "KEY" }, + [KNOT_RRTYPE_AAAA] = { { 16, KNOT_RDATA_WF_END }, "AAAA" }, + [KNOT_RRTYPE_LOC] = { { 16, KNOT_RDATA_WF_END }, "LOC" }, + [KNOT_RRTYPE_SRV] = { { 6, KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "SRV" }, + [KNOT_RRTYPE_NAPTR] = { { KNOT_RDATA_WF_NAPTR_HEADER, + KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "NAPTR" }, + [KNOT_RRTYPE_KX] = { { 2, KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_END }, "KX" }, + [KNOT_RRTYPE_CERT] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "CERT" }, + [KNOT_RRTYPE_DNAME] = { { KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_END }, "DNAME" }, + [KNOT_RRTYPE_OPT] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "OPT" }, + [KNOT_RRTYPE_APL] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "APL" }, + [KNOT_RRTYPE_DS] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "DS" }, + [KNOT_RRTYPE_SSHFP] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "SSHFP" }, + [KNOT_RRTYPE_IPSECKEY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "IPSECKEY" }, + [KNOT_RRTYPE_RRSIG] = { { 18, KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "RRSIG" }, + [KNOT_RRTYPE_NSEC] = { { KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "NSEC" }, + [KNOT_RRTYPE_DNSKEY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "DNSKEY" }, + [KNOT_RRTYPE_DHCID] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "DHCID" }, + [KNOT_RRTYPE_NSEC3] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "NSEC3" }, + [KNOT_RRTYPE_NSEC3PARAM] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "NSEC3PARAM" }, + [KNOT_RRTYPE_TLSA] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "TLSA" }, + [KNOT_RRTYPE_SMIMEA] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "SMIMEA" }, + [KNOT_RRTYPE_CDS] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "CDS" }, + [KNOT_RRTYPE_CDNSKEY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "CDNSKEY" }, + [KNOT_RRTYPE_OPENPGPKEY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "OPENPGPKEY" }, + [KNOT_RRTYPE_CSYNC] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "CSYNC" }, + [KNOT_RRTYPE_ZONEMD] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "ZONEMD" }, + [KNOT_RRTYPE_SVCB] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "SVCB" }, + [KNOT_RRTYPE_HTTPS] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "HTTPS" }, + [KNOT_RRTYPE_SPF] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "SPF" }, + [KNOT_RRTYPE_NID] = { { 10, KNOT_RDATA_WF_END }, "NID" }, + [KNOT_RRTYPE_L32] = { { 6, KNOT_RDATA_WF_END }, "L32" }, + [KNOT_RRTYPE_L64] = { { 10, KNOT_RDATA_WF_END }, "L64" }, + [KNOT_RRTYPE_LP] = { { 2, KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_END }, "LP" }, + [KNOT_RRTYPE_EUI48] = { { 6, KNOT_RDATA_WF_END }, "EUI48" }, + [KNOT_RRTYPE_EUI64] = { { 8, KNOT_RDATA_WF_END }, "EUI64" }, + [KNOT_RRTYPE_TKEY] = { { KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "TKEY" }, + [KNOT_RRTYPE_TSIG] = { { KNOT_RDATA_WF_FIXED_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "TSIG" }, + [KNOT_RRTYPE_IXFR] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "IXFR" }, + [KNOT_RRTYPE_AXFR] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "AXFR" }, + [KNOT_RRTYPE_ANY] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "ANY" }, + [KNOT_RRTYPE_URI] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "URI" }, + [KNOT_RRTYPE_CAA] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "CAA" }, +}; + +#define MAX_RRTYPE sizeof(rdata_descriptors) / sizeof(knot_rdata_descriptor_t) - 1 + +/*! + * \brief Some (OBSOLETE) RR type descriptors. + */ +static const knot_rdata_descriptor_t obsolete_rdata_descriptors[] = { + [0] = { { KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, NULL }, + [KNOT_RRTYPE_MD] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MD" }, + [KNOT_RRTYPE_MF] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MF" }, + [KNOT_RRTYPE_MB] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MB" }, + [KNOT_RRTYPE_MG] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MG" }, + [KNOT_RRTYPE_MR] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "MR" }, + [KNOT_RRTYPE_PX] = { { 2, KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_END }, "PX" }, + [KNOT_RRTYPE_NXT] = { { KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + KNOT_RDATA_WF_REMAINDER, + KNOT_RDATA_WF_END }, "NXT" }, +}; + +_public_ +const knot_rdata_descriptor_t *knot_get_rdata_descriptor(const uint16_t type) +{ + if (type <= MAX_RRTYPE && rdata_descriptors[type].type_name != NULL) { + return &rdata_descriptors[type]; + } else { + return &rdata_descriptors[0]; + } +} + +_public_ +const knot_rdata_descriptor_t *knot_get_obsolete_rdata_descriptor(const uint16_t type) +{ + if (type <= KNOT_RRTYPE_NXT && + obsolete_rdata_descriptors[type].type_name != NULL) { + return &obsolete_rdata_descriptors[type]; + } else { + return &obsolete_rdata_descriptors[0]; + } +} + +_public_ +int knot_rrtype_to_string(const uint16_t rrtype, + char *out, + const size_t out_len) +{ + if (out == NULL) { + return -1; + } + + int ret; + + const knot_rdata_descriptor_t *descr = knot_get_rdata_descriptor(rrtype); + + if (descr->type_name != NULL) { + ret = snprintf(out, out_len, "%s", descr->type_name); + } else { + ret = snprintf(out, out_len, "TYPE%u", rrtype); + } + + if (ret <= 0 || (size_t)ret >= out_len) { + return -1; + } else { + return ret; + } +} + +_public_ +int knot_rrtype_from_string(const char *name, uint16_t *num) +{ + if (name == NULL || num == NULL) { + return -1; + } + + int i; + char *end; + unsigned long n; + + // Try to find name in descriptors table. + for (i = 0; i <= MAX_RRTYPE; i++) { + if (rdata_descriptors[i].type_name != NULL && + strcasecmp(rdata_descriptors[i].type_name, name) == 0) { + *num = i; + return 0; + } + } + + // Type name must begin with TYPE. + if (strncasecmp(name, "TYPE", 4) != 0) { + return -1; + } else { + name += 4; + } + + // The rest must be a number. + n = strtoul(name, &end, 10); + if (end == name || *end != '\0' || n > UINT16_MAX) { + return -1; + } + + *num = n; + return 0; +} + +_public_ +int knot_rrclass_to_string(const uint16_t rrclass, + char *out, + const size_t out_len) +{ + if (out == NULL) { + return -1; + } + + int ret; + + if (rrclass <= KNOT_CLASS_ANY && dns_classes[rrclass] != NULL) { + ret = snprintf(out, out_len, "%s", dns_classes[rrclass]); + } else { + ret = snprintf(out, out_len, "CLASS%u", rrclass); + } + + if (ret <= 0 || (size_t)ret >= out_len) { + return -1; + } else { + return ret; + } +} + +_public_ +int knot_rrclass_from_string(const char *name, uint16_t *num) +{ + if (name == NULL || num == NULL) { + return -1; + } + + int i; + char *end; + unsigned long n; + + // Try to find the name in classes table. + for (i = 0; i <= KNOT_CLASS_ANY; i++) { + if (dns_classes[i] != NULL && + strcasecmp(dns_classes[i], name) == 0) { + *num = i; + return 0; + } + } + + // Class name must begin with CLASS. + if (strncasecmp(name, "CLASS", 5) != 0) { + return -1; + } else { + name += 5; + } + + // The rest must be a number. + n = strtoul(name, &end, 10); + if (end == name || *end != '\0' || n > UINT16_MAX) { + return -1; + } + + *num = n; + return 0; +} + +_public_ +int knot_rrtype_is_metatype(const uint16_t type) +{ + return type == KNOT_RRTYPE_SIG || + type == KNOT_RRTYPE_OPT || + type == KNOT_RRTYPE_TKEY || + type == KNOT_RRTYPE_TSIG || + type == KNOT_RRTYPE_IXFR || + type == KNOT_RRTYPE_AXFR || + type == KNOT_RRTYPE_ANY; +} + +_public_ +int knot_rrtype_is_dnssec(const uint16_t type) +{ + return type == KNOT_RRTYPE_DNSKEY || + type == KNOT_RRTYPE_RRSIG || + type == KNOT_RRTYPE_NSEC || + type == KNOT_RRTYPE_NSEC3 || + type == KNOT_RRTYPE_NSEC3PARAM || + type == KNOT_RRTYPE_CDNSKEY || + type == KNOT_RRTYPE_CDS; +} + +_public_ +int knot_rrtype_additional_needed(const uint16_t type) +{ + return type == KNOT_RRTYPE_NS || + type == KNOT_RRTYPE_MX || + type == KNOT_RRTYPE_SRV; +} + +_public_ +bool knot_rrtype_should_be_lowercased(const uint16_t type) +{ + return type == KNOT_RRTYPE_NS || + type == KNOT_RRTYPE_MD || + type == KNOT_RRTYPE_MF || + type == KNOT_RRTYPE_CNAME || + type == KNOT_RRTYPE_SOA || + type == KNOT_RRTYPE_MB || + type == KNOT_RRTYPE_MG || + type == KNOT_RRTYPE_MR || + type == KNOT_RRTYPE_PTR || + type == KNOT_RRTYPE_MINFO || + type == KNOT_RRTYPE_MX || + type == KNOT_RRTYPE_RP || + type == KNOT_RRTYPE_AFSDB || + type == KNOT_RRTYPE_RT || + type == KNOT_RRTYPE_SIG || + type == KNOT_RRTYPE_PX || + type == KNOT_RRTYPE_NXT || + type == KNOT_RRTYPE_NAPTR || + type == KNOT_RRTYPE_KX || + type == KNOT_RRTYPE_SRV || + type == KNOT_RRTYPE_DNAME || + type == KNOT_RRTYPE_RRSIG; +} + +_public_ +int knot_opt_code_to_string(const uint16_t code, char *out, const size_t out_len) +{ + if (out == NULL) { + return -1; + } + + const char *name = NULL; + + switch (code) { + case 1: name = "LLQ"; break; + case 2: name = "UL"; break; + case 3: name = "NSID"; break; + case 5: name = "DAU"; break; + case 6: name = "DHU"; break; + case 7: name = "N3U"; break; + case 8: name = "EDNS-CLIENT-SUBNET"; break; + case 9: name = "EDNS-EXPIRE"; break; + case 10: name = "COOKIE"; break; + case 11: name = "EDNS-TCP-KEEPALIVE"; break; + case 12: name = "PADDING"; break; + case 13: name = "CHAIN"; break; + case 14: name = "EDNS-KEY-TAG"; break; + } + + int ret; + + if (name != NULL) { + ret = snprintf(out, out_len, "%s", name); + } else { + ret = snprintf(out, out_len, "CODE%u", code); + } + + if (ret <= 0 || (size_t)ret >= out_len) { + return -1; + } else { + return ret; + } +} diff --git a/src/libknot/descriptor.h b/src/libknot/descriptor.h new file mode 100644 index 0000000..72a5867 --- /dev/null +++ b/src/libknot/descriptor.h @@ -0,0 +1,305 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \addtogroup rr + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#define KNOT_MAX_RDATA_BLOCKS 8 +#define KNOT_MAX_RDATA_DNAMES 2 // Update this when defining new RR types! + +/*! + * \brief Resource record class codes. + * + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml + */ +enum knot_rr_class { + KNOT_CLASS_IN = 1, + KNOT_CLASS_CH = 3, + KNOT_CLASS_NONE = 254, + KNOT_CLASS_ANY = 255 +}; + +/*! + * \brief Resource record type constants. + * + * References: + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml + * RFC 3597#4 + * + * METATYPE: Contains DNS data that can't be in a zone file. + * QTYPE: Specifies DNS query type; can't be in a zone file. + */ +enum knot_rr_type { + KNOT_RRTYPE_A = 1, /*!< An IPv4 host address. */ + KNOT_RRTYPE_NS = 2, /*!< An authoritative name server. */ + + KNOT_RRTYPE_CNAME = 5, /*!< The canonical name for an alias. */ + KNOT_RRTYPE_SOA = 6, /*!< The start of a zone of authority. */ + + KNOT_RRTYPE_NULL = 10, /*!< METATYPE. Used in RFC 8145. */ + + KNOT_RRTYPE_PTR = 12, /*!< A domain name pointer. */ + KNOT_RRTYPE_HINFO = 13, /*!< A host information. */ + KNOT_RRTYPE_MINFO = 14, /*!< A mailbox information. */ + KNOT_RRTYPE_MX = 15, /*!< Mail exchange. */ + KNOT_RRTYPE_TXT = 16, /*!< Text strings. */ + KNOT_RRTYPE_RP = 17, /*!< For responsible person. */ + KNOT_RRTYPE_AFSDB = 18, /*!< For AFS Data Base location. */ + + KNOT_RRTYPE_RT = 21, /*!< For route through. */ + + KNOT_RRTYPE_SIG = 24, /*!< METATYPE. Transaction signature. */ + KNOT_RRTYPE_KEY = 25, /*!< For security key. */ + + KNOT_RRTYPE_AAAA = 28, /*!< IPv6 address. */ + KNOT_RRTYPE_LOC = 29, /*!< Location information. */ + + KNOT_RRTYPE_SRV = 33, /*!< Server selection. */ + + KNOT_RRTYPE_NAPTR = 35, /*!< Naming authority pointer . */ + KNOT_RRTYPE_KX = 36, /*!< Key exchanger. */ + KNOT_RRTYPE_CERT = 37, /*!< Certificate record. */ + + KNOT_RRTYPE_DNAME = 39, /*!< Delegation name. */ + + KNOT_RRTYPE_OPT = 41, /*!< METATYPE. Option for EDNS. */ + KNOT_RRTYPE_APL = 42, /*!< Address prefix list. */ + KNOT_RRTYPE_DS = 43, /*!< Delegation signer. */ + KNOT_RRTYPE_SSHFP = 44, /*!< SSH public key fingerprint. */ + KNOT_RRTYPE_IPSECKEY = 45, /*!< IPSEC key. */ + KNOT_RRTYPE_RRSIG = 46, /*!< DNSSEC signature. */ + KNOT_RRTYPE_NSEC = 47, /*!< Next-secure record. */ + KNOT_RRTYPE_DNSKEY = 48, /*!< DNS key. */ + KNOT_RRTYPE_DHCID = 49, /*!< DHCP identifier. */ + KNOT_RRTYPE_NSEC3 = 50, /*!< NSEC version 3. */ + KNOT_RRTYPE_NSEC3PARAM = 51, /*!< NSEC3 parameters. */ + KNOT_RRTYPE_TLSA = 52, /*!< DANE record. */ + KNOT_RRTYPE_SMIMEA = 53, /*!< S/MIME cert association. */ + + KNOT_RRTYPE_CDS = 59, /*!< Child delegation signer. */ + KNOT_RRTYPE_CDNSKEY = 60, /*!< Child DNS key. */ + KNOT_RRTYPE_OPENPGPKEY = 61, /*!< OpenPGP Key. */ + KNOT_RRTYPE_CSYNC = 62, /*!< Child-To-Parent synchronization. */ + KNOT_RRTYPE_ZONEMD = 63, /*!< Message digest for DNS zone. */ + KNOT_RRTYPE_SVCB = 64, /*!< Service Binding. */ + KNOT_RRTYPE_HTTPS = 65, /*!< HTTPS Binding. */ + + KNOT_RRTYPE_SPF = 99, /*!< Sender policy framework. */ + + KNOT_RRTYPE_NID = 104, /*!< Node identifier. */ + KNOT_RRTYPE_L32 = 105, /*!< 32-bit network locator. */ + KNOT_RRTYPE_L64 = 106, /*!< 64-bit network locator. */ + KNOT_RRTYPE_LP = 107, /*!< Subnetwork name. */ + KNOT_RRTYPE_EUI48 = 108, /*!< 48-bit extended unique identifier. */ + KNOT_RRTYPE_EUI64 = 109, /*!< 64-bit extended unique identifier. */ + + KNOT_RRTYPE_TKEY = 249, /*!< METATYPE. Transaction key. */ + KNOT_RRTYPE_TSIG = 250, /*!< METATYPE. Transaction signature. */ + KNOT_RRTYPE_IXFR = 251, /*!< QTYPE. Incremental zone transfer. */ + KNOT_RRTYPE_AXFR = 252, /*!< QTYPE. Authoritative zone transfer. */ + + KNOT_RRTYPE_ANY = 255, /*!< QTYPE. Any record. */ + KNOT_RRTYPE_URI = 256, /*!< Uniform resource identifier. */ + KNOT_RRTYPE_CAA = 257, /*!< Certification authority restriction. */ +}; + +/*! + * \brief Some (OBSOLETE) resource record type constants. + * + * References: + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml + * RFC 3597#4 + * + * \note These records can contain compressed domain name in rdata so + * it is important to know the position of them during transfers. + */ +enum knot_obsolete_rr_type { + KNOT_RRTYPE_MD = 3, + KNOT_RRTYPE_MF = 4, + KNOT_RRTYPE_MB = 7, + KNOT_RRTYPE_MG = 8, + KNOT_RRTYPE_MR = 9, + KNOT_RRTYPE_PX = 26, + KNOT_RRTYPE_NXT = 30 +}; + +/*! + * \brief Constants characterising the wire format of RDATA items. + */ +enum knot_rdata_wireformat { + /*!< Dname must not be compressed. */ + KNOT_RDATA_WF_FIXED_DNAME = -10, + /*!< Dname can be both compressed and decompressed. */ + KNOT_RDATA_WF_COMPRESSIBLE_DNAME, + /*!< Dname can be decompressed. */ + KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME, + /*!< Initial part of NAPTR record before dname. */ + KNOT_RDATA_WF_NAPTR_HEADER, + /*!< Final part of a record. */ + KNOT_RDATA_WF_REMAINDER, + /*!< The last descriptor in array. */ + KNOT_RDATA_WF_END = 0 +}; + +/*! + * \brief Structure describing rdata. + */ +typedef struct { + /*!< Item types describing rdata. */ + const int block_types[KNOT_MAX_RDATA_BLOCKS]; + /*!< RR type name. */ + const char *type_name; +} knot_rdata_descriptor_t; + +/*! + * \brief Gets rdata descriptor for given RR name. + * + * \param type Mnemonic of RR type whose descriptor should be returned. + * + * \retval RR descriptor for given name, NULL descriptor if + * unknown type. + */ +const knot_rdata_descriptor_t *knot_get_rdata_descriptor(const uint16_t type); + +/*! + * \brief Gets rdata descriptor for given RR name (obsolete version). + * + * \param type Mnemonic of RR type whose descriptor should be returned. + * + * \retval RR descriptor for given name, NULL descriptor if + * unknown type. + */ +const knot_rdata_descriptor_t *knot_get_obsolete_rdata_descriptor(const uint16_t type); + +/*! + * \brief Converts numeric type representation to mnemonic string. + * + * \param rrtype Type RR type code to be converted. + * \param out Output buffer. + * \param out_len Length of the output buffer. + * + * \retval Length of output string. + * \retval -1 if error. + */ +int knot_rrtype_to_string(const uint16_t rrtype, + char *out, + const size_t out_len); + +/*! + * \brief Converts mnemonic string representation of a type to numeric one. + * + * \param name Mnemonic string to be converted. + * \param num Output variable. + * + * \retval 0 if OK. + * \retval -1 if error. + */ +int knot_rrtype_from_string(const char *name, uint16_t *num); + +/*! + * \brief Converts numeric class representation to the string one. + * + * \param rrclass Class code to be converted. + * \param out Output buffer. + * \param out_len Length of the output buffer. + * + * \retval Length of output string. + * \retval -1 if error. + */ +int knot_rrclass_to_string(const uint16_t rrclass, + char *out, + const size_t out_len); + +/*! + * \brief Converts string representation of a class to numeric one. + * + * \param name Mnemonic string to be converted. + * \param num Output variable. + * + * \retval 0 if OK. + * \retval -1 if error. + */ +int knot_rrclass_from_string(const char *name, uint16_t *num); + +/*! + * \brief Checks if given item is one of metatypes or qtypes. + * + * \param type Item value. + * + * \retval > 0 if YES. + * \retval 0 if NO. + */ +int knot_rrtype_is_metatype(const uint16_t type); + +/*! + * \brief Checks if given item is one of the DNSSEC types. + * + * \param type Item value. + * + * \retval > 0 if YES. + * \retval 0 if NO. + */ +int knot_rrtype_is_dnssec(const uint16_t type); + +/*! + * \brief Checks whether the given type requires additional processing. + * + * Only MX, NS and SRV types require additional processing. + * + * \param type Type to check. + * + * \retval <> 0 if additional processing is needed for \a qtype. + * \retval 0 otherwise. + */ +int knot_rrtype_additional_needed(const uint16_t type); + +/*! + * \brief Checks whether the RDATA domain names should be lowercased in + * canonical format of RRSet of the given type. + * + * Types that should be lowercased are accorrding to RFC 4034, Section 6.2, + * except for NSEC (updated by RFC 6840, Section 5.1) and A6 (not supported). + * + * \param type RRSet type to check. + * + * \retval true If RDATA dnames for type should be lowercased in canonical format. + * \retval false Otherwise. + */ +bool knot_rrtype_should_be_lowercased(const uint16_t type); + +/*! + * \brief Translates option code to string. + * + * \param code Code of the option to translate. + * \param out Buffer for the output string. + * \param out_len The available size of the buffer. + * + * \retval Length of output string. + * \retval -1 if error. + */ +int knot_opt_code_to_string(const uint16_t code, char *out, const size_t out_len); + +/*! @} */ diff --git a/src/libknot/dname.c b/src/libknot/dname.c new file mode 100644 index 0000000..0643971 --- /dev/null +++ b/src/libknot/dname.c @@ -0,0 +1,781 @@ +/* Copyright (C) 2020 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/dname.h" +#include "libknot/errcode.h" +#include "libknot/packet/wire.h" +#include "contrib/ctype.h" +#include "contrib/mempattern.h" +#include "contrib/tolower.h" + +static bool label_is_equal(const uint8_t *lb1, const uint8_t *lb2, bool no_case) +{ + if (*lb1 != *lb2) { + return false; + } + + if (no_case) { + uint8_t len = *lb1; + for (uint8_t i = 1; i <= len; i++) { + if (knot_tolower(lb1[i]) != knot_tolower(lb2[i])) { + return false; + } + } + + return true; + } else { + return memcmp(lb1 + 1, lb2 + 1, *lb1) == 0; + } +} + +/*! + * \brief Align name end-to-end and return number of common suffix labels. + * + * \param d1 Domain name1. + * \param d1_labels Number of labels in d1. + * \param d2 Domain name2. + * \param d2_labels Number of labels in d2. + */ +static int dname_align(const uint8_t **d1, uint8_t d1_labels, + const uint8_t **d2, uint8_t d2_labels) +{ + assert(d1 && d2); + + for (unsigned j = d1_labels; j < d2_labels; ++j) { + *d2 = knot_wire_next_label(*d2, NULL); + } + + for (unsigned j = d2_labels; j < d1_labels; ++j) { + *d1 = knot_wire_next_label(*d1, NULL); + } + + return (d1_labels < d2_labels) ? d1_labels : d2_labels; +} + +_public_ +int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp, + const uint8_t *pkt) +{ + if (name == NULL || name == endp) { + return KNOT_EINVAL; + } + + int wire_len = 0; + int name_len = 1; /* Keep \x00 terminal label in advance. */ + bool is_compressed = false; + + while (*name != '\0') { + /* Check bounds (must have at least 2 octets remaining). */ + if (name + 2 > endp) { + return KNOT_EMALF; + } + + if (knot_wire_is_pointer(name)) { + /* Check that the pointer points backwards + * otherwise it could result in infinite loop + */ + if (pkt == NULL) { + return KNOT_EINVAL; + } + uint16_t ptr = knot_wire_get_pointer(name); + if (ptr >= (name - pkt)) { + return KNOT_EMALF; + } + + name = pkt + ptr; /* Hop to compressed label */ + if (!is_compressed) { /* Measure compressed size only */ + wire_len += sizeof(uint16_t); + is_compressed = true; + } + } else { + /* Check label length. */ + if (*name > KNOT_DNAME_MAXLABELLEN) { + return KNOT_EMALF; + } + /* Check if there's enough space. */ + int lblen = *name + 1; + if (name_len + lblen > KNOT_DNAME_MAXLEN) { + return KNOT_EMALF; + } + /* Update wire size only for noncompressed part. */ + name_len += lblen; + if (!is_compressed) { + wire_len += lblen; + } + /* Hop to next label. */ + name += lblen; + } + + /* Check bounds (must have at least 1 octet). */ + if (name + 1 > endp) { + return KNOT_EMALF; + } + } + + if (!is_compressed) { /* Terminal label. */ + wire_len += 1; + } + + return wire_len; +} + +_public_ +size_t knot_dname_store(knot_dname_storage_t dst, const knot_dname_t *name) +{ + if (dst == NULL || name == NULL) { + return 0; + } + + size_t len = knot_dname_size(name); + assert(len <= KNOT_DNAME_MAXLEN); + memcpy(dst, name, len); + + return len; +} + +_public_ +knot_dname_t *knot_dname_copy(const knot_dname_t *name, knot_mm_t *mm) +{ + if (name == NULL) { + return NULL; + } + + size_t len = knot_dname_size(name); + knot_dname_t *dst = mm_alloc(mm, len); + if (dst == NULL) { + return NULL; + } + memcpy(dst, name, len); + + return dst; +} + +_public_ +int knot_dname_to_wire(uint8_t *dst, const knot_dname_t *src, size_t maxlen) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + size_t len = knot_dname_size(src); + if (len > maxlen) { + return KNOT_ESPACE; + } + memcpy(dst, src, len); + + return len; +} + +_public_ +int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src, + size_t maxlen, const uint8_t *pkt) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + /* Seek first real label occurrence. */ + src = knot_wire_seek_label(src, pkt); + + /* Unpack rest of the labels. */ + int len = 0; + while (*src != '\0') { + uint8_t lblen = *src + 1; + if (len + lblen > maxlen) { + return KNOT_ESPACE; + } + memcpy(dst + len, src, lblen); + len += lblen; + src = knot_wire_next_label(src, pkt); + } + + /* Terminal label */ + if (len + 1 > maxlen) { + return KNOT_EINVAL; + } + + *(dst + len) = '\0'; + return len + 1; +} + +_public_ +char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen) +{ + if (name == NULL) { + return NULL; + } + + size_t dname_size = knot_dname_size(name); + + /* Check the size for len(dname) + 1 char termination. */ + size_t alloc_size = (dst == NULL) ? dname_size + 1 : maxlen; + if (alloc_size < dname_size + 1) { + return NULL; + } + + char *res = (dst == NULL) ? malloc(alloc_size) : dst; + if (res == NULL) { + return NULL; + } + + uint8_t label_len = 0; + size_t str_len = 0; + + for (unsigned i = 0; i < dname_size; i++) { + uint8_t c = name[i]; + + /* Read next label size. */ + if (label_len == 0) { + label_len = c; + + /* Write label separation. */ + if (str_len > 0 || dname_size == 1) { + if (alloc_size <= str_len + 1) { + goto dname_to_str_failed; + } + res[str_len++] = '.'; + } + + continue; + } + + if (is_alnum(c) || c == '-' || c == '_' || c == '*' || + c == '/') { + if (alloc_size <= str_len + 1) { + goto dname_to_str_failed; + } + res[str_len++] = c; + } else if (is_punct(c) && c != '#') { + /* Exclusion of '#' character is to avoid possible + * collision with rdata hex notation '\#'. So it is + * encoded in \ddd notation. + */ + + if (dst != NULL) { + if (maxlen <= str_len + 2) { + goto dname_to_str_failed; + } + } else { + /* Extend output buffer for \x format. */ + alloc_size += 1; + char *extended = realloc(res, alloc_size); + if (extended == NULL) { + goto dname_to_str_failed; + } + res = extended; + } + + /* Write encoded character. */ + res[str_len++] = '\\'; + res[str_len++] = c; + } else { + if (dst != NULL) { + if (maxlen <= str_len + 4) { + goto dname_to_str_failed; + } + } else { + /* Extend output buffer for \DDD format. */ + alloc_size += 3; + char *extended = realloc(res, alloc_size); + if (extended == NULL) { + goto dname_to_str_failed; + } + res = extended; + } + + /* Write encoded character. */ + int ret = snprintf(res + str_len, alloc_size - str_len, + "\\%03u", c); + if (ret <= 0 || ret >= alloc_size - str_len) { + goto dname_to_str_failed; + } + + str_len += ret; + } + + label_len--; + } + + /* String_termination. */ + assert(str_len < alloc_size); + res[str_len] = 0; + + return res; + +dname_to_str_failed: + + if (dst == NULL) { + free(res); + } + + return NULL; +} + +_public_ +knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen) +{ + if (name == NULL) { + return NULL; + } + + size_t name_len = strlen(name); + if (name_len == 0) { + return NULL; + } + + /* Wire size estimation. */ + size_t alloc_size = maxlen; + if (dst == NULL) { + /* Check for the root label. */ + if (name[0] == '.') { + /* Just the root dname can begin with a dot. */ + if (name_len > 1) { + return NULL; + } + name_len = 0; /* Skip the following parsing. */ + alloc_size = 1; + } else if (name[name_len - 1] != '.') { /* Check for non-FQDN. */ + alloc_size = 1 + name_len + 1; + } else { + alloc_size = 1 + name_len ; /* + 1 ~ first label length. */ + } + } + + /* The minimal (root) dname takes 1 byte. */ + if (alloc_size == 0) { + return NULL; + } + + /* Check the maximal wire size. */ + if (alloc_size > KNOT_DNAME_MAXLEN) { + alloc_size = KNOT_DNAME_MAXLEN; + } + + /* Prepare output buffer. */ + uint8_t *wire = (dst == NULL) ? malloc(alloc_size) : dst; + if (wire == NULL) { + return NULL; + } + + uint8_t *label = wire; + uint8_t *wire_pos = wire + 1; + uint8_t *wire_end = wire + alloc_size; + + /* Initialize the first label (root label). */ + *label = 0; + + const uint8_t *ch = (const uint8_t *)name; + const uint8_t *end = ch + name_len; + + while (ch < end) { + /* Check the output buffer for enough space. */ + if (wire_pos >= wire_end) { + goto dname_from_str_failed; + } + + switch (*ch) { + case '.': + /* Check for invalid zero-length label. */ + if (*label == 0 && name_len > 1) { + goto dname_from_str_failed; + } + label = wire_pos++; + *label = 0; + break; + case '\\': + ch++; + + /* At least one more character is required OR + * check for maximal label length. + */ + if (ch == end || ++(*label) > KNOT_DNAME_MAXLABELLEN) { + goto dname_from_str_failed; + } + + /* Check for \DDD notation. */ + if (is_digit(*ch)) { + /* Check for next two digits. */ + if (ch + 2 >= end || + !is_digit(*(ch + 1)) || + !is_digit(*(ch + 2))) { + goto dname_from_str_failed; + } + + uint32_t num = (*(ch + 0) - '0') * 100 + + (*(ch + 1) - '0') * 10 + + (*(ch + 2) - '0') * 1; + if (num > UINT8_MAX) { + goto dname_from_str_failed; + } + *(wire_pos++) = num; + ch +=2; + } else { + *(wire_pos++) = *ch; + } + break; + default: + /* Check for maximal label length. */ + if (++(*label) > KNOT_DNAME_MAXLABELLEN) { + goto dname_from_str_failed; + } + *(wire_pos++) = *ch; + } + ch++; + } + + /* Check for non-FQDN name. */ + if (*label > 0) { + if (wire_pos >= wire_end) { + goto dname_from_str_failed; + } + *(wire_pos++) = 0; + } + + /* Reduce output buffer if the size is overestimated. */ + if (wire_pos < wire_end && dst == NULL) { + uint8_t *reduced = realloc(wire, wire_pos - wire); + if (reduced == NULL) { + goto dname_from_str_failed; + } + wire = reduced; + } + + return wire; + +dname_from_str_failed: + + if (dst == NULL) { + free(wire); + } + + return NULL; +} + +_public_ +void knot_dname_to_lower(knot_dname_t *name) +{ + if (name == NULL) { + return; + } + + while (*name != '\0') { + uint8_t len = *name; + for (uint8_t i = 1; i <= len; ++i) { + name[i] = knot_tolower(name[i]); + } + name += 1 + len; + } +} + +_public_ +size_t knot_dname_size(const knot_dname_t *name) +{ + if (name == NULL) { + return 0; + } + + /* Count name size without terminal label. */ + size_t len = 0; + while (*name != '\0' && !knot_wire_is_pointer(name)) { + uint8_t lblen = *name + 1; + len += lblen; + name += lblen; + } + + if (knot_wire_is_pointer(name)) { + /* Add 2-octet compression pointer. */ + return len + 2; + } else { + /* Add 1-octet terminal label. */ + return len + 1; + } +} + +_public_ +size_t knot_dname_realsize(const knot_dname_t *name, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + /* Seek first real label occurrence. */ + name = knot_wire_seek_label(name, pkt); + + size_t len = 0; + while (*name != '\0') { + len += *name + 1; + name = knot_wire_next_label(name, pkt); + } + + /* Add 1-octet terminal label. */ + return len + 1; +} + +_public_ +size_t knot_dname_matched_labels(const knot_dname_t *d1, const knot_dname_t *d2) +{ + /* Count labels. */ + size_t l1 = knot_dname_labels(d1, NULL); + size_t l2 = knot_dname_labels(d2, NULL); + if (l1 == 0 || l2 == 0) { + return 0; + } + + /* Align end-to-end to common suffix. */ + int common = dname_align(&d1, l1, &d2, l2); + + /* Count longest chain leading to root label. */ + size_t matched = 0; + while (common > 0) { + if (label_is_equal(d1, d2, false)) { + ++matched; + } else { + matched = 0; /* Broken chain. */ + } + + /* Next label. */ + d1 = knot_wire_next_label(d1, NULL); + d2 = knot_wire_next_label(d2, NULL); + --common; + } + + return matched; +} + +_public_ +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned labels, + const knot_dname_t *suffix, knot_mm_t *mm) +{ + if (name == NULL) { + return NULL; + } + + /* Calculate prefix and suffix lengths. */ + size_t dname_lbs = knot_dname_labels(name, NULL); + if (dname_lbs < labels) { + return NULL; + } + size_t prefix_lbs = dname_lbs - labels; + + size_t prefix_len = knot_dname_prefixlen(name, prefix_lbs, NULL); + size_t suffix_len = knot_dname_size(suffix); + if (prefix_len == 0 || suffix_len == 0) { + return NULL; + } + + /* Create target name. */ + size_t new_len = prefix_len + suffix_len; + knot_dname_t *out = mm_alloc(mm, new_len); + if (out == NULL) { + return NULL; + } + + /* Copy prefix. */ + uint8_t *dst = out; + while (prefix_lbs > 0) { + memcpy(dst, name, *name + 1); + dst += *name + 1; + name = knot_wire_next_label(name, NULL); + --prefix_lbs; + } + + /* Copy suffix. */ + while (*suffix != '\0') { + memcpy(dst, suffix, *suffix + 1); + dst += *suffix + 1; + suffix = knot_wire_next_label(suffix, NULL); + } + + *dst = '\0'; + return out; +} + +_public_ +void knot_dname_free(knot_dname_t *name, knot_mm_t *mm) +{ + if (name == NULL) { + return; + } + + mm_free(mm, name); +} + +_public_ +int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2) +{ + if (d1 == NULL) { + return -1; + } else if (d2 == NULL) { + return 1; + } + + /* Convert to lookup format. */ + knot_dname_storage_t lf1_storage; + knot_dname_storage_t lf2_storage; + + uint8_t *lf1 = knot_dname_lf(d1, lf1_storage); + uint8_t *lf2 = knot_dname_lf(d2, lf2_storage); + assert(lf1 && lf2); + + /* Compare common part. */ + uint8_t common = lf1[0]; + if (common > lf2[0]) { + common = lf2[0]; + } + int ret = memcmp(lf1 + 1, lf2 + 1, common); + if (ret != 0) { + return ret; + } + + /* If they match, compare lengths. */ + if (lf1[0] < lf2[0]) { + return -1; + } else if (lf1[0] > lf2[0]) { + return 1; + } else { + return 0; + } +} + +inline static bool dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2, bool no_case) +{ + if (d1 == NULL || d2 == NULL) { + return false; + } + + while (*d1 != '\0' || *d2 != '\0') { + if (label_is_equal(d1, d2, no_case)) { + d1 = knot_wire_next_label(d1, NULL); + d2 = knot_wire_next_label(d2, NULL); + } else { + return false; + } + } + + return true; +} + +_public_ +bool knot_dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return dname_is_equal(d1, d2, false); +} + +_public_ +bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return dname_is_equal(d1, d2, true); +} + +_public_ +size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + /* Zero labels means no prefix. */ + if (nlabels == 0) { + return 0; + } + + /* Seek first real label occurrence. */ + name = knot_wire_seek_label(name, pkt); + + size_t len = 0; + while (*name != '\0') { + len += *name + 1; + name = knot_wire_next_label(name, pkt); + if (--nlabels == 0) { /* Count N first labels only. */ + break; + } + } + + return len; +} + +_public_ +size_t knot_dname_labels(const uint8_t *name, const uint8_t *pkt) +{ + if (name == NULL) { + return 0; + } + + size_t count = 0; + while (*name != '\0') { + ++count; + name = knot_wire_next_label(name, pkt); + if (name == NULL) { + return 0; + } + } + + return count; +} + +_public_ +uint8_t *knot_dname_lf(const knot_dname_t *src, knot_dname_storage_t storage) +{ + if (src == NULL || storage == NULL) { + return NULL; + } + + /* Writing from the end. */ + storage[KNOT_DNAME_MAXLEN - 1] = '\0'; + size_t idx = KNOT_DNAME_MAXLEN - 1; + + while (*src != 0) { + size_t len = *src + 1; + + assert(idx >= len); + idx -= len; + memcpy(&storage[idx], src, len); + storage[idx] = '\0'; + + src += len; + } + + assert(KNOT_DNAME_MAXLEN >= 1 + idx); + storage[idx] = KNOT_DNAME_MAXLEN - 1 - idx; + + return &storage[idx]; +} + +_public_ +int knot_dname_in_bailiwick(const knot_dname_t *name, const knot_dname_t *bailiwick) +{ + if (name == NULL || bailiwick == NULL) { + return KNOT_EINVAL; + } + + int label_diff = knot_dname_labels(name, NULL) - knot_dname_labels(bailiwick, NULL); + if (label_diff < 0) { + return KNOT_EOUTOFZONE; + } + + for (int i = 0; i < label_diff; ++i) { + name = knot_wire_next_label(name, NULL); + } + + return knot_dname_is_equal(name, bailiwick) ? label_diff : KNOT_EOUTOFZONE; +} diff --git a/src/libknot/dname.h b/src/libknot/dname.h new file mode 100644 index 0000000..f9a0351 --- /dev/null +++ b/src/libknot/dname.h @@ -0,0 +1,344 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Domain name structure and API for manipulating it. + * + * \addtogroup dname + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/mm_ctx.h" + +/*! \brief Type representing a domain name in wire format. */ +typedef uint8_t knot_dname_t; + +/*! \brief Local domain name storage. */ +typedef uint8_t knot_dname_storage_t[KNOT_DNAME_MAXLEN]; + +/*! \brief Local textual domain name storage. */ +typedef char knot_dname_txt_storage_t[KNOT_DNAME_TXT_MAXLEN + 1]; + +/*! + * \brief Check dname on the wire for constraints. + * + * If the name passes such checks, it is safe to be used in rest of the functions. + * + * \param name Name on the wire. + * \param endp Name boundary. + * \param pkt Wire. + * + * \retval (compressed) size of the domain name. + * \retval KNOT_EINVAL + * \retval KNOT_EMALF + */ +_pure_ _mustcheck_ +int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp, + const uint8_t *pkt); + +/*! + * \brief Duplicates the given domain name to a local storage. + * + * \param dst Destination storage. + * \param name Domain name to be copied. + * + * \retval size of the domain name. + * \retval 0 if invalid argument. + */ +_mustcheck_ +size_t knot_dname_store(knot_dname_storage_t dst, const knot_dname_t *name); + +/*! + * \brief Duplicates the given domain name. + * + * \param name Domain name to be copied. + * \param mm Memory context. + * + * \return New domain name which is an exact copy of \a name. + */ +_mustcheck_ +knot_dname_t *knot_dname_copy(const knot_dname_t *name, knot_mm_t *mm); + +/*! + * \brief Copy name to wire as is, no compression pointer expansion will be done. + * + * \param dst Destination wire. + * \param src Source name. + * \param maxlen Maximum wire length. + * + * \return the number of bytes written or negative error code + */ +int knot_dname_to_wire(uint8_t *dst, const knot_dname_t *src, size_t maxlen); + +/*! + * \brief Write unpacked name (i.e. compression pointers expanded) + * + * \note The function is very similar to the knot_dname_to_wire(), except + * it expands compression pointers. E.g. you want to use knot_dname_unpack() + * if you copy a dname from incoming packet to some persistent storage. + * And you want to use knot_dname_to_wire() if you know the name is not + * compressed or you want to copy it 1:1. + * + * \param dst Destination wire. + * \param src Source name. + * \param maxlen Maximum destination wire size. + * \param pkt Name packet wire (for compression pointers). + * + * \return number of bytes written + */ +int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src, + size_t maxlen, const uint8_t *pkt); + +/*! + * \brief Converts the given domain name to its string representation. + * + * \note Output buffer is allocated automatically if dst is NULL. + * + * \param dst Output buffer. + * \param name Domain name to be converted. + * \param maxlen Output buffer length. + * + * \return 0-terminated string if successful, NULL if error. + */ +char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen); + +/*! + * \brief This function is a shortcut for \ref knot_dname_to_str with + * no output buffer parameters. + */ +_mustcheck_ +static inline char *knot_dname_to_str_alloc(const knot_dname_t *name) +{ + return knot_dname_to_str(NULL, name, 0); +} + +/*! + * \brief Creates a dname structure from domain name given in presentation + * format. + * + * \note The resulting FQDN is stored in the wire format. + * \note Output buffer is allocated automatically if dst is NULL. + * + * \param dst Output buffer. + * \param name Domain name in presentation format (labels separated by dots, + * '\0' terminated). + * \param maxlen Output buffer length. + * + * \return New dname if successful, NULL if error. + */ +knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen); + +/*! + * \brief This function is a shortcut for \ref knot_dname_from_str with + * no output buffer parameters. + */ +_mustcheck_ +static inline knot_dname_t *knot_dname_from_str_alloc(const char *name) +{ + return knot_dname_from_str(NULL, name, 0); +} + +/*! + * \brief Convert name to lowercase. + * + * \param name Domain name to be converted. + */ +void knot_dname_to_lower(knot_dname_t *name); + +/*! + * \brief Returns size of the given domain name. + * + * \note If the domain name is compressed, the length of not compressed part + * is returned. + * + * \param name Domain name to get the size of. + * + * \retval size of the domain name. + * \retval 0 if invalid argument. + */ +_pure_ +size_t knot_dname_size(const knot_dname_t *name); + +/*! + * \brief Returns full size of the given domain name (expanded compression ptrs). + * + * \param name Domain name to get the size of. + * \param pkt Related packet (or NULL if unpacked) + * + * \retval size of the domain name. + * \retval 0 if invalid argument. + */ +_pure_ +size_t knot_dname_realsize(const knot_dname_t *name, const uint8_t *pkt); + +/*! + * \brief Checks if the domain name is a wildcard. + * + * \param name Domain name to check. + * + * \retval true if \a dname is a wildcard domain name. + * \retval false otherwise. + */ +static inline +bool knot_dname_is_wildcard(const knot_dname_t *name) +{ + return name != NULL && name[0] == 1 && name[1] == '*'; +} + +/*! + * \brief Returns the number of labels common for the two domain names (counted + * from the rightmost label. + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \return Number of labels common for the two domain names. + */ +_pure_ +size_t knot_dname_matched_labels(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Replaces the suffix of given size in one domain name with other domain + * name. + * + * \param name Domain name where to replace the suffix. + * \param labels Size of the suffix to be replaced. + * \param suffix New suffix to be used as a replacement. + * \param mm Memory context. + * + * \return New domain name created by replacing suffix of \a dname of size + * \a size with \a suffix. + */ +_mustcheck_ +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned labels, + const knot_dname_t *suffix, knot_mm_t *mm); + +/*! + * \brief Destroys the given domain name. + * + * \param name Domain name to be destroyed. + * \param mm Memory context. + */ +void knot_dname_free(knot_dname_t *name, knot_mm_t *mm); + +/*! + * \brief Compares two domain names by labels (case sensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + * \retval 0 if the domain names are identical. + */ +_pure_ +int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Compares two domain names (case sensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval true if the domain names are identical + * \retval false if the domain names are NOT identical + */ +_pure_ +bool knot_dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Compares two domain names (case insensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval true if the domain names are equal + * \retval false if the domain names are NOT equal + */ +_pure_ +bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Count length of the N first labels. + * + * \param name Domain name. + * \param nlabels First N labels. + * \param pkt Related packet (or NULL if not compressed). + * + * \return Length of the prefix. + */ +_pure_ +size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt); + +/*! + * \brief Return number of labels in the domain name. + * + * Terminal nullbyte is not counted. + * + * \param name Domain name. + * \param pkt Related packet (or NULL if not compressed). + * + * \return Number of labels. + */ +_pure_ +size_t knot_dname_labels(const uint8_t *name, const uint8_t *pkt); + +/*! + * \brief Convert domain name from wire to the lookup format. + * + * Formats names from rightmost label to the leftmost, separated by the lowest + * possible character (\\x00). Sorting such formatted names also gives + * correct canonical order (for NSEC/NSEC3). The first byte of the output + * contains length of the output. + * + * Examples: + * Name: lake.example.com. + * Wire: \\x04lake\\x07example\\x03com\\x00 + * Lookup: \\x11com\\x00example\\x00lake\\x00 + * + * Name: . + * Wire: \\x00 + * Lookup: \\x00 + * + * \param src Source domain name. + * \param storage Memory to store converted name into. Don't use directly! + * + * \retval Lookup format if successful (pointer into the storage). + * \retval NULL on invalid parameters. + */ +uint8_t *knot_dname_lf(const knot_dname_t *src, knot_dname_storage_t storage); + +/*! + * \brief Check whether a domain name is under another one and how deep. + * + * \param name The longer name to check. + * \param bailiwick The shorter name to check. + * + * \retval >=0 a subdomain nested this many labels. + * \retval <0 not a subdomain (KNOT_EOUTOFZONE) or another error (KNOT_EINVAL). + */ +int knot_dname_in_bailiwick(const knot_dname_t *name, const knot_dname_t *bailiwick); + +/*! @} */ diff --git a/src/libknot/endian.h b/src/libknot/endian.h new file mode 100644 index 0000000..7323b12 --- /dev/null +++ b/src/libknot/endian.h @@ -0,0 +1,51 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Endian dependent integer operations. + * + * \addtogroup wire + * @{ + */ + +#pragma once + +#if defined(__linux__) || defined(__gnu_hurd__) || \ + (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) +# include <endian.h> +#elif defined(__FreeBSD__) || defined(__NetBSD__) +# include <sys/endian.h> +#elif defined(__OpenBSD__) || defined(__sun) +# include <endian.h> +#elif defined(__APPLE__) +# include <libkern/OSByteOrder.h> +# define be16toh(x) OSSwapBigToHostInt16(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htobe64(x) OSSwapHostToBigInt64(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +#endif + +/*! @} */ diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h new file mode 100644 index 0000000..6f7f9e7 --- /dev/null +++ b/src/libknot/errcode.h @@ -0,0 +1,265 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Knot error codes. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include <errno.h> + +/*! \brief Error codes used in the library. */ +enum knot_error { + KNOT_EOK = 0, + + /* Directly mapped error codes. */ + KNOT_ENOMEM = -ENOMEM, + KNOT_EINVAL = -EINVAL, + KNOT_ENOTSUP = -ENOTSUP, + KNOT_EBUSY = -EBUSY, + KNOT_EAGAIN = -EAGAIN, + KNOT_ENOBUFS = -ENOBUFS, + KNOT_EMFILE = -EMFILE, + KNOT_ENFILE = -ENFILE, + KNOT_EACCES = -EACCES, + KNOT_EISCONN = -EISCONN, + KNOT_ECONNREFUSED = -ECONNREFUSED, + KNOT_EALREADY = -EALREADY, + KNOT_ECONNRESET = -ECONNRESET, + KNOT_ECONNABORTED = -ECONNABORTED, + KNOT_ENETRESET = -ENETRESET, + KNOT_EHOSTUNREACH = -EHOSTUNREACH, + KNOT_ENETUNREACH = -ENETUNREACH, + KNOT_EHOSTDOWN = -EHOSTDOWN, + KNOT_ENETDOWN = -ENETDOWN, + KNOT_EADDRINUSE = -EADDRINUSE, + KNOT_ENOENT = -ENOENT, + KNOT_EEXIST = -EEXIST, + KNOT_ERANGE = -ERANGE, + KNOT_EADDRNOTAVAIL = -EADDRNOTAVAIL, + + KNOT_ERRNO_ERROR = -500, + + KNOT_ERROR_MIN = -1000, + + /* General errors. */ + KNOT_ERROR = KNOT_ERROR_MIN, + KNOT_EPARSEFAIL, + KNOT_ESEMCHECK, + KNOT_EUPTODATE, + KNOT_EFEWDATA, + KNOT_ESPACE, + KNOT_EMALF, + KNOT_ENSEC3PAR, + KNOT_ENSEC3CHAIN, + KNOT_EOUTOFZONE, + KNOT_EZONEINVAL, + KNOT_ENOZONE, + KNOT_ENONODE, + KNOT_ENORECORD, + KNOT_EISRECORD, + KNOT_ENOMASTER, + KNOT_EPREREQ, + KNOT_ETTL, + KNOT_ENOXFR, + KNOT_EDENIED, + KNOT_ECONN, + KNOT_ETIMEOUT, + KNOT_ENODIFF, + KNOT_ENOTSIG, + KNOT_ELIMIT, + KNOT_EZONESIZE, + KNOT_EOF, + KNOT_ESYSTEM, + KNOT_EFILE, + KNOT_ESOAINVAL, + KNOT_ETRAIL, + KNOT_EPROCESSING, + KNOT_EPROGRESS, + KNOT_ELOOP, + KNOT_EPROGRAM, + KNOT_EFD, + KNOT_ENOPARAM, + + KNOT_GENERAL_ERROR = -900, + + /* Control states. */ + KNOT_CTL_ESTOP, + KNOT_CTL_EZONE, + + /* Network errors. */ + KNOT_NET_EADDR, + KNOT_NET_ESOCKET, + KNOT_NET_ECONNECT, + KNOT_NET_ESEND, + KNOT_NET_ERECV, + KNOT_NET_ETIMEOUT, + + /* Encoding errors. */ + KNOT_BASE64_ESIZE, + KNOT_BASE64_ECHAR, + KNOT_BASE32HEX_ESIZE, + KNOT_BASE32HEX_ECHAR, + + /* TSIG errors. */ + KNOT_TSIG_EBADSIG, + KNOT_TSIG_EBADKEY, + KNOT_TSIG_EBADTIME, + KNOT_TSIG_EBADTRUNC, + + /* DNSSEC errors. */ + KNOT_DNSSEC_EMISSINGKEYTYPE, + KNOT_DNSSEC_ENOKEY, + KNOT_DNSSEC_ENOSIG, + KNOT_DNSSEC_ENSEC_BITMAP, + KNOT_DNSSEC_ENSEC_CHAIN, + KNOT_DNSSEC_ENSEC3_OPTOUT, + + /* Yparser errors. */ + KNOT_YP_ECHAR_TAB, + KNOT_YP_EINVAL_ITEM, + KNOT_YP_EINVAL_ID, + KNOT_YP_EINVAL_DATA, + KNOT_YP_EINVAL_INDENT, + KNOT_YP_ENOTSUP_DATA, + KNOT_YP_ENOTSUP_ID, + KNOT_YP_ENODATA, + KNOT_YP_ENOID, + + /* Configuration errors. */ + KNOT_CONF_ENOTINIT, + KNOT_CONF_EVERSION, + KNOT_CONF_EREDEFINE, + + /* Transaction errors. */ + KNOT_TXN_EEXISTS, + KNOT_TXN_ENOTEXISTS, + + /* DNSSEC errors. */ + KNOT_INVALID_PUBLIC_KEY, + KNOT_INVALID_PRIVATE_KEY, + KNOT_INVALID_KEY_ALGORITHM, + KNOT_INVALID_KEY_SIZE, + KNOT_INVALID_KEY_ID, + KNOT_INVALID_KEY_NAME, + KNOT_NO_PUBLIC_KEY, + KNOT_NO_PRIVATE_KEY, + KNOT_NO_READY_KEY, + + KNOT_ERROR_MAX = -501 +}; + +/*! + * \brief Map POSIX errno code to Knot error code. + * + * \param code Errno code to transform (set -1 to use the current errno). + * \param dflt_error Default return value, if code is unknown. + * + * \return Mapped errno or the value of dflt_error if unknown. + */ +inline static int knot_map_errno_code_def(int code, int dflt_error) +{ + if (code < 0) { + code = errno; + } + + typedef struct { + int errno_code; + int libknot_code; + } err_table_t; + + #define ERR_ITEM(name) { name, KNOT_##name } + static const err_table_t errno_to_errcode[] = { + ERR_ITEM(ENOMEM), + ERR_ITEM(EINVAL), + ERR_ITEM(ENOTSUP), + ERR_ITEM(EBUSY), + ERR_ITEM(EAGAIN), + ERR_ITEM(ENOBUFS), + ERR_ITEM(EMFILE), + ERR_ITEM(ENFILE), + ERR_ITEM(EACCES), + ERR_ITEM(EISCONN), + ERR_ITEM(ECONNREFUSED), + ERR_ITEM(EALREADY), + ERR_ITEM(ECONNRESET), + ERR_ITEM(ECONNABORTED), + ERR_ITEM(ENETRESET), + ERR_ITEM(EHOSTUNREACH), + ERR_ITEM(ENETUNREACH), + ERR_ITEM(EHOSTDOWN), + ERR_ITEM(ENETDOWN), + ERR_ITEM(EADDRINUSE), + ERR_ITEM(ENOENT), + ERR_ITEM(EEXIST), + ERR_ITEM(ERANGE), + ERR_ITEM(EADDRNOTAVAIL), + + /* Terminator - the value isn't used. */ + { 0, KNOT_ERRNO_ERROR } + }; + #undef ERR_ITEM + + const err_table_t *err = errno_to_errcode; + + while (err->errno_code != 0 && err->errno_code != code) { + err++; + } + + return err->errno_code != 0 ? err->libknot_code : dflt_error; +} + +/*! + * \brief Map POSIX errno code to Knot error code. + * + * \param code Errno code to transform (set -1 to use the current errno). + * + * \return Mapped errno or KNOT_ERRNO_ERROR if unknown. + */ +inline static int knot_map_errno_code(int code) +{ + return knot_map_errno_code_def(code, KNOT_ERRNO_ERROR); +} + +/*! + * \brief Get a POSIX errno mapped to Knot error code. + * + * \param dflt_error Default return value, if code is unknown. + * + * \return Mapped errno. + */ +inline static int knot_map_errno_def(int dflt_error) +{ + return knot_map_errno_code_def(-1, dflt_error); +} + +/*! + * \brief Get a POSIX errno mapped to Knot error code. + * + * \return Mapped errno or KNOT_ERRNO_ERROR if unknown. + */ +inline static int knot_map_errno(void) +{ + return knot_map_errno_code_def(-1, KNOT_ERRNO_ERROR); +} + +/*! @} */ diff --git a/src/libknot/error.c b/src/libknot/error.c new file mode 100644 index 0000000..e0484ef --- /dev/null +++ b/src/libknot/error.c @@ -0,0 +1,232 @@ +/* Copyright (C) 2020 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <lmdb.h> + +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "libdnssec/error.h" + +struct error { + int code; + const char *message; +}; + +static const struct error errors[] = { + { KNOT_EOK, "OK" }, + + /* Directly mapped error codes. */ + { KNOT_ENOMEM, "not enough memory" }, + { KNOT_EINVAL, "invalid parameter" }, + { KNOT_ENOTSUP, "operation not supported" }, + { KNOT_EBUSY, "requested resource is busy" }, + { KNOT_EAGAIN, "OS lacked necessary resources" }, + { KNOT_ENOBUFS, "no buffers" }, + { KNOT_EMFILE, "too many open files" }, + { KNOT_ENFILE, "too many open files in system" }, + { KNOT_EACCES, "operation not permitted" }, + { KNOT_EISCONN, "already connected" }, + { KNOT_ECONNREFUSED, "connection refused" }, + { KNOT_EALREADY, "operation already in progress" }, + { KNOT_ECONNRESET, "connection reset by peer" }, + { KNOT_ECONNABORTED, "connection aborted" }, + { KNOT_ENETRESET, "connection aborted by network" }, + { KNOT_EHOSTUNREACH, "host is unreachable" }, + { KNOT_ENETUNREACH, "network is unreachable" }, + { KNOT_EHOSTDOWN, "host is down" }, + { KNOT_ENETDOWN, "network is down" }, + { KNOT_EADDRINUSE, "address already in use" }, + { KNOT_ENOENT, "not exists" }, + { KNOT_EEXIST, "already exists" }, + { KNOT_ERANGE, "value is out of range" }, + { KNOT_EADDRNOTAVAIL, "address is not available" }, + + { KNOT_ERRNO_ERROR, "unknown system error" }, + + /* General errors. */ + { KNOT_ERROR, "failed" }, + { KNOT_EPARSEFAIL, "parser failed" }, + { KNOT_ESEMCHECK, "semantic check" }, + { KNOT_EUPTODATE, "zone is up-to-date" }, + { KNOT_EFEWDATA, "not enough data to parse" }, + { KNOT_ESPACE, "not enough space provided" }, + { KNOT_EMALF, "malformed data" }, + { KNOT_ENSEC3PAR, "missing or wrong NSEC3PARAM record" }, + { KNOT_ENSEC3CHAIN, "missing or wrong NSEC3 chain in the zone" }, + { KNOT_EOUTOFZONE, "name does not belong to the zone" }, + { KNOT_EZONEINVAL, "invalid zone file" }, + { KNOT_ENOZONE, "no such zone found" }, + { KNOT_ENONODE, "no such node in zone found" }, + { KNOT_ENORECORD, "no such record in zone found" }, + { KNOT_EISRECORD, "such record already exists in zone" }, + { KNOT_ENOMASTER, "no usable master" }, + { KNOT_EPREREQ, "UPDATE prerequisity not met" }, + { KNOT_ETTL, "TTL mismatch" }, + { KNOT_ENOXFR, "transfer was not sent" }, + { KNOT_EDENIED, "not allowed" }, + { KNOT_ECONN, "connection reset" }, + { KNOT_ETIMEOUT, "connection timeout" }, + { KNOT_ENODIFF, "cannot create zone diff" }, + { KNOT_ENOTSIG, "expected a TSIG or SIG(0)" }, + { KNOT_ELIMIT, "exceeded limit" }, + { KNOT_EZONESIZE, "zone size exceeded" }, + { KNOT_EOF, "end of file" }, + { KNOT_ESYSTEM, "system error" }, + { KNOT_EFILE, "file error" }, + { KNOT_ESOAINVAL, "SOA mismatch" }, + { KNOT_ETRAIL, "trailing data" }, + { KNOT_EPROCESSING, "processing error" }, + { KNOT_EPROGRESS, "in progress" }, + { KNOT_ELOOP, "loop detected" }, + { KNOT_EPROGRAM, "program not loaded" }, + { KNOT_EFD, "file descriptor error" }, + { KNOT_ENOPARAM, "missing parameter" }, + + { KNOT_GENERAL_ERROR, "unknown general error" }, + + /* Control states. */ + { KNOT_CTL_ESTOP, "stopping server" }, + { KNOT_CTL_EZONE, "operation failed for some zones" }, + + /* Network errors. */ + { KNOT_NET_EADDR, "bad address or host name" }, + { KNOT_NET_ESOCKET, "can't create socket" }, + { KNOT_NET_ECONNECT, "can't connect" }, + { KNOT_NET_ESEND, "can't send data" }, + { KNOT_NET_ERECV, "can't receive data" }, + { KNOT_NET_ETIMEOUT, "network timeout" }, + + /* Encoding errors. */ + { KNOT_BASE64_ESIZE, "invalid base64 string length" }, + { KNOT_BASE64_ECHAR, "invalid base64 character" }, + { KNOT_BASE32HEX_ESIZE, "invalid base32hex string length" }, + { KNOT_BASE32HEX_ECHAR, "invalid base32hex character" }, + + /* TSIG errors. */ + { KNOT_TSIG_EBADSIG, "failed to verify TSIG" }, + { KNOT_TSIG_EBADKEY, "TSIG key not recognized or invalid" }, + { KNOT_TSIG_EBADTIME, "TSIG out of time window" }, + { KNOT_TSIG_EBADTRUNC, "TSIG bad truncation" }, + + /* DNSSEC errors. */ + { KNOT_DNSSEC_ENOKEY, "no keys for signing" }, + { KNOT_DNSSEC_EMISSINGKEYTYPE, "missing active KSK or ZSK" }, + { KNOT_DNSSEC_ENOSIG, "no valid signature for a record" }, + { KNOT_DNSSEC_ENSEC_BITMAP, "missing NSEC(3) record or wrong bitmap" }, + { KNOT_DNSSEC_ENSEC_CHAIN, "inconsistent NSEC(3) chain" }, + { KNOT_DNSSEC_ENSEC3_OPTOUT, "wrong NSEC3 opt-out" }, + + /* Yparser errors. */ + { KNOT_YP_ECHAR_TAB, "tabulator character is not allowed" }, + { KNOT_YP_EINVAL_ITEM, "invalid item" }, + { KNOT_YP_EINVAL_ID, "invalid identifier" }, + { KNOT_YP_EINVAL_DATA, "invalid value" }, + { KNOT_YP_EINVAL_INDENT, "invalid indentation" }, + { KNOT_YP_ENOTSUP_DATA, "value not supported" }, + { KNOT_YP_ENOTSUP_ID, "identifier not supported" }, + { KNOT_YP_ENODATA, "missing value" }, + { KNOT_YP_ENOID, "missing identifier" }, + + /* Configuration errors. */ + { KNOT_CONF_ENOTINIT, "config DB not initialized" }, + { KNOT_CONF_EVERSION, "invalid config DB version" }, + { KNOT_CONF_EREDEFINE, "duplicate identifier" }, + + /* Transaction errors. */ + { KNOT_TXN_EEXISTS, "too many transactions" }, + { KNOT_TXN_ENOTEXISTS, "no active transaction" }, + + /* DNSSEC errors. */ + { KNOT_INVALID_PUBLIC_KEY, "invalid public key" }, + { KNOT_INVALID_PRIVATE_KEY, "invalid private key" }, + { KNOT_INVALID_KEY_ALGORITHM, "invalid key algorithm" }, + { KNOT_INVALID_KEY_SIZE, "invalid key size" }, + { KNOT_INVALID_KEY_ID, "invalid key ID" }, + { KNOT_INVALID_KEY_NAME, "invalid key name" }, + { KNOT_NO_PUBLIC_KEY, "no public key" }, + { KNOT_NO_PRIVATE_KEY, "no private key" }, + { KNOT_NO_READY_KEY, "no key ready for submission" }, + + /* Terminator */ + { KNOT_ERROR, NULL } +}; + +/*! + * \brief Lookup error message by error code. + */ +static const char *lookup_message(int code) +{ + for (const struct error *e = errors; e->message; e++) { + if (e->code == code) { + return e->message; + } + } + + return NULL; +} + +_public_ +int knot_error_from_libdnssec(int libdnssec_errcode) +{ + switch (libdnssec_errcode) { + case DNSSEC_ERROR: + return KNOT_ERROR; + case DNSSEC_MALFORMED_DATA: + return KNOT_EMALF; + case DNSSEC_NOT_FOUND: + return KNOT_ENOENT; + case DNSSEC_NO_PUBLIC_KEY: + case DNSSEC_NO_PRIVATE_KEY: + return KNOT_DNSSEC_ENOKEY; + // EOK, EINVAL, ENOMEM and ENOENT are identical, no need to translate + case DNSSEC_INVALID_PUBLIC_KEY ... DNSSEC_INVALID_KEY_NAME: + return libdnssec_errcode + - DNSSEC_INVALID_PUBLIC_KEY + KNOT_INVALID_PUBLIC_KEY; + default: + return libdnssec_errcode; + } +} + +_public_ +const char *knot_strerror(int code) +{ + const char *msg; + + switch (code) { + case INT_MIN: // Cannot convert to a positive value. + code = KNOT_ERROR; + // FALLTHROUGH + case KNOT_ERROR_MIN ... KNOT_EOK: + msg = lookup_message(code); break; + case DNSSEC_ERROR_MIN ... DNSSEC_ERROR_MAX: + msg = dnssec_strerror(code); break; + case MDB_KEYEXIST ... MDB_LAST_ERRCODE: + msg = mdb_strerror(code); break; + default: + msg = NULL; + } + + if (msg != NULL) { + return msg; + } else { + // strerror_r would be better but it requires thread local storage. + return strerror(abs(code)); + } +} diff --git a/src/libknot/error.h b/src/libknot/error.h new file mode 100644 index 0000000..4403497 --- /dev/null +++ b/src/libknot/error.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2016 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/>. + */ + +/*! +* \file +* +* \brief Error codes and function for getting error message. +* +* \addtogroup libknot +* @{ +*/ + +#pragma once + +#include "libknot/errcode.h" + +/*! + * \brief Returns error message for the given error code. + * + * \param code Error code. + * + * \return String containing the error message. + */ +const char *knot_strerror(int code); + +/*! + * \brief Translates error code from libdnssec into libknot. + * + * This is just temporary until everything from libdnssec moved to libknot. + * + * \param libdnssec_errcode Error code from libdnssec + * + * \return Error code. + */ +int knot_error_from_libdnssec(int libdnssec_errcode); + +/*! @} */ diff --git a/src/libknot/libknot.h b/src/libknot/libknot.h new file mode 100644 index 0000000..b0de886 --- /dev/null +++ b/src/libknot/libknot.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Convenience header for including whole library. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include "libknot/version.h" +#include "libknot/cookies.h" +#include "libknot/codes.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/dname.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/error.h" +#include "libknot/lookup.h" +#include "libknot/mm_ctx.h" +#include "libknot/rdata.h" +#include "libknot/rdataset.h" +#include "libknot/rrset-dump.h" +#include "libknot/rrset.h" +#include "libknot/tsig-op.h" +#include "libknot/tsig.h" +#include "libknot/control/control.h" +#include "libknot/db/db.h" +#include "libknot/db/db_lmdb.h" +#include "libknot/db/db_trie.h" +#include "libknot/packet/compr.h" +#include "libknot/packet/pkt.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/packet/wire.h" +#include "libknot/rrtype/dnskey.h" +#include "libknot/rrtype/ds.h" +#include "libknot/rrtype/naptr.h" +#include "libknot/rrtype/nsec.h" +#include "libknot/rrtype/nsec3.h" +#include "libknot/rrtype/nsec3param.h" +#include "libknot/rrtype/opt.h" +#include "libknot/rrtype/rdname.h" +#include "libknot/rrtype/rrsig.h" +#include "libknot/rrtype/soa.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/wire.h" +#if 0 +#include "libknot/xdp/xdp.h" +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/eth.h" +#endif + +/*! @} */ diff --git a/src/libknot/libknot.h.in b/src/libknot/libknot.h.in new file mode 100644 index 0000000..323618f --- /dev/null +++ b/src/libknot/libknot.h.in @@ -0,0 +1,71 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Convenience header for including whole library. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include "libknot/version.h" +#include "libknot/cookies.h" +#include "libknot/codes.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/dname.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/error.h" +#include "libknot/lookup.h" +#include "libknot/mm_ctx.h" +#include "libknot/rdata.h" +#include "libknot/rdataset.h" +#include "libknot/rrset-dump.h" +#include "libknot/rrset.h" +#include "libknot/tsig-op.h" +#include "libknot/tsig.h" +#include "libknot/control/control.h" +#include "libknot/db/db.h" +#include "libknot/db/db_lmdb.h" +#include "libknot/db/db_trie.h" +#include "libknot/packet/compr.h" +#include "libknot/packet/pkt.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/packet/wire.h" +#include "libknot/rrtype/dnskey.h" +#include "libknot/rrtype/ds.h" +#include "libknot/rrtype/naptr.h" +#include "libknot/rrtype/nsec.h" +#include "libknot/rrtype/nsec3.h" +#include "libknot/rrtype/nsec3param.h" +#include "libknot/rrtype/opt.h" +#include "libknot/rrtype/rdname.h" +#include "libknot/rrtype/rrsig.h" +#include "libknot/rrtype/soa.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/wire.h" +#if @XDP_VISIBLE_HEADERS@ +#include "libknot/xdp/xdp.h" +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/eth.h" +#endif + +/*! @} */ diff --git a/src/libknot/lookup.h b/src/libknot/lookup.h new file mode 100644 index 0000000..8599aa3 --- /dev/null +++ b/src/libknot/lookup.h @@ -0,0 +1,89 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief A general purpose lookup table. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include <string.h> +#include <strings.h> + +/*! + * \brief A general purpose lookup table. + */ +typedef struct knot_lookup { + int id; + const char *name; +} knot_lookup_t; + +/*! + * \brief Looks up the given name in the lookup table. + * + * \param table Lookup table. + * \param name Name to look up. + * + * \return Item in the lookup table with the given name or NULL if no such is + * present. + */ +inline static const knot_lookup_t *knot_lookup_by_name(const knot_lookup_t *table, const char *name) +{ + if (table == NULL || name == NULL) { + return NULL; + } + + while (table->name != NULL) { + if (strcasecmp(name, table->name) == 0) { + return table; + } + table++; + } + + return NULL; +} + +/*! + * \brief Looks up the given id in the lookup table. + * + * \param table Lookup table. + * \param id ID to look up. + * + * \return Item in the lookup table with the given id or NULL if no such is + * present. + */ +inline static const knot_lookup_t *knot_lookup_by_id(const knot_lookup_t *table, int id) +{ + if (table == NULL) { + return NULL; + } + + while (table->name != NULL) { + if (table->id == id) { + return table; + } + table++; + } + + return NULL; +} + +/*! @} */ diff --git a/src/libknot/mm_ctx.h b/src/libknot/mm_ctx.h new file mode 100644 index 0000000..8f0544c --- /dev/null +++ b/src/libknot/mm_ctx.h @@ -0,0 +1,41 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \brief Memory allocation context. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#include <stddef.h> + +/* Memory allocation function prototypes. */ +typedef void* (*knot_mm_alloc_t)(void *ctx, size_t len); +typedef void (*knot_mm_free_t)(void *p); + +/*! \brief Memory allocation context. */ +typedef struct knot_mm { + void *ctx; /* \note Must be first */ + knot_mm_alloc_t alloc; + knot_mm_free_t free; +} knot_mm_t; + +/*! @} */ diff --git a/src/libknot/packet/compr.h b/src/libknot/packet/compr.h new file mode 100644 index 0000000..ea14d64 --- /dev/null +++ b/src/libknot/packet/compr.h @@ -0,0 +1,101 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Name compression API. + * + * \addtogroup pkt + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include "libknot/packet/wire.h" + +/*! \brief Compression hint type. */ +enum knot_compr_hint { + KNOT_COMPR_HINT_NONE = 0, /* No hint. */ + KNOT_COMPR_HINT_NOCOMP = 1, /* Don't compress. */ + KNOT_COMPR_HINT_QNAME = KNOT_WIRE_HEADER_SIZE /* Name is QNAME. */ +}; + +/*! \brief Compression hint array offsets. */ +enum knot_compr_offset { + KNOT_COMPR_HINT_OWNER = 0, /* First element in the array is RR owner. */ + KNOT_COMPR_HINT_RDATA = 1, /* First name in RDATA is at offset 1. */ + KNOT_COMPR_HINT_COUNT = 16 /* Maximum number of stored hints per-RR. */ +}; + +/* + * \note A little bit about how compression hints work. + * + * We're storing a RRSet say 'abcd. CNAME [0]net. [1]com.' (owner=abcd. 2 RRs). + * The owner 'abcd.' is same for both RRs, we put it at the offset 0 in rrinfo.compress_ptr + * The names 'net.' and 'com.' are in the RDATA, therefore go to offsets 1 and 2. + * Now this is useful when solving additionals for example, because we can scan + * rrinfo for this RRSet and we know that 'net.' name is at the hint 1 and that leads + * to packet position N. With that, we just put the pointer in without any calculation. + * This is also useful for positive answers, where we know the RRSet owner is always QNAME. + * All in all, we just remember the positions of written domain names. + */ + +/*! \brief Additional information about RRSet position and compression hints. */ +typedef struct { + uint16_t pos; /* RRSet position in the packet. */ + uint16_t flags; /* RRSet flags. */ + uint16_t compress_ptr[KNOT_COMPR_HINT_COUNT]; /* Array of compr. ptr hints. */ +} knot_rrinfo_t; + +/*! + * \brief Name compression context. + */ +typedef struct knot_compr { + uint8_t *wire; /* Packet wireformat. */ + knot_rrinfo_t *rrinfo; /* Hints for current RRSet. */ + struct { + uint16_t pos; /* Position of current suffix. */ + uint8_t labels; /* Label count of the suffix. */ + } suffix; +} knot_compr_t; + +/*! + * \brief Retrieve compression hint from given offset. + */ +static inline uint16_t knot_compr_hint(const knot_rrinfo_t *info, uint16_t hint_id) +{ + if (hint_id < KNOT_COMPR_HINT_COUNT) { + return info->compress_ptr[hint_id]; + } else { + return KNOT_COMPR_HINT_NONE; + } +} + +/*! + * \brief Store compression hint for given offset. + */ +static inline void knot_compr_hint_set(knot_rrinfo_t *info, uint16_t hint_id, + uint16_t val, uint16_t len) +{ + if ((hint_id < KNOT_COMPR_HINT_COUNT) && (val + len < KNOT_WIRE_PTR_MAX)) { + info->compress_ptr[hint_id] = val; + } +} + +/*! @} */ diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c new file mode 100644 index 0000000..ddda72e --- /dev/null +++ b/src/libknot/packet/pkt.c @@ -0,0 +1,825 @@ +/* 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 the old data. */ + memcpy(rr_info, pkt->rr_info, pkt->rrset_allocd * sizeof(knot_rrinfo_t)); + memcpy(rr, pkt->rr, pkt->rrset_allocd * sizeof(knot_rrset_t)); + + /* Reassign and free old data. */ + 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, sizeof(knot_pkt_t)); + + /* 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); + + pkt->qname_size = query->qname_size; + if (query->qname_size == 0) { + /* Reset question count if malformed. */ + knot_wire_set_qdcount(pkt->wire, 0); + } + + /* 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); +} + +_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 wireformat. */ + 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 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; + + 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 : ""; +} diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h new file mode 100644 index 0000000..fa8ae5f --- /dev/null +++ b/src/libknot/packet/pkt.h @@ -0,0 +1,404 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Structure for holding DNS packet data and metadata. + * + * \addtogroup pkt + * @{ + */ + +#pragma once + +#include <assert.h> +#include <stdint.h> + +#include "libknot/consts.h" +#include "libknot/dname.h" +#include "libknot/mm_ctx.h" +#include "libknot/rrset.h" +#include "libknot/rrtype/opt.h" +#include "libknot/packet/wire.h" +#include "libknot/packet/compr.h" +#include "libknot/wire.h" + +/* Number of packet sections (ANSWER, AUTHORITY, ADDITIONAL). */ +#define KNOT_PKT_SECTIONS 3 + +/*! + * \brief Packet flags. + */ +enum { + KNOT_PF_NULL = 0 << 0, /*!< No flags. */ + KNOT_PF_FREE = 1 << 1, /*!< Free with packet. */ + KNOT_PF_NOTRUNC = 1 << 2, /*!< Don't truncate. */ + KNOT_PF_CHECKDUP = 1 << 3, /*!< Check for duplicates. */ + KNOT_PF_KEEPWIRE = 1 << 4, /*!< Keep wireformat untouched when parsing. */ + KNOT_PF_NOCANON = 1 << 5, /*!< Don't canonicalize rrsets during parsing. */ + KNOT_PF_ORIGTTL = 1 << 6, /*!< Write RRSIGs with their original TTL. */ + KNOT_PF_SOAMINTTL = 1 << 7, /*!< Write SOA with its minimum-ttl as TTL. */ +}; + +typedef struct knot_pkt knot_pkt_t; + +/*! + * \brief Packet section. + * Points to RRSet and RRSet info arrays in the packet. + * This structure is required for random access to packet sections. + */ +typedef struct { + knot_pkt_t *pkt; /*!< Owner. */ + uint16_t pos; /*!< Position in the rr/rrinfo fields in packet. */ + uint16_t count; /*!< Number of RRSets in this section. */ +} knot_pktsection_t; + +/*! + * \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. */ + size_t parsed; /*!< Parsed size. */ + uint16_t reserved; /*!< Reserved space. */ + uint16_t qname_size; /*!< QNAME size. */ + uint16_t rrset_count; /*!< Packet RRSet count. */ + uint16_t flags; /*!< Packet flags. */ + + knot_rrset_t *opt_rr; /*!< OPT RR included in the packet. */ + knot_rrset_t *tsig_rr; /*!< TSIG RR stored in the packet. */ + + /*! 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; + } tsig_wire; + + /* Packet sections. */ + knot_section_t current; + knot_pktsection_t sections[KNOT_PKT_SECTIONS]; + + /* Packet RRSet (meta)data. */ + size_t rrset_allocd; + knot_rrinfo_t *rr_info; + knot_rrset_t *rr; + + knot_mm_t mm; /*!< Memory allocation context. */ + + knot_compr_t compr; /*!< Compression context. */ +}; + +/*! + * \brief Create new packet over existing memory, or allocate new from memory context. + * + * \note Packet is allocated from given memory context. + * + * \param wire If NULL, memory of 'len' size shall be allocated. + * Otherwise pointer is used for the wire format of the packet. + * \param len Wire format length. + * \param mm Memory context (NULL for default). + * \return New packet or NULL. + */ +knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm); + +/*! + * \brief Copy packet. + * + * \note Current implementation is not very efficient, as it re-parses the wire. + * + * \param dst Target packet. + * \param src Source packet. + * + * \return new packet or NULL + */ +int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src); + +/*! + * \brief Initialized response from query packet. + * + * \note Question is not checked, it is expected to be checked already. + * + * \param pkt Given packet. + * \param query Query. + * \return KNOT_EOK, KNOT_EINVAL, KNOT_ESPACE + */ +int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query); + +/*! \brief Reinitialize packet for another use. */ +void knot_pkt_clear(knot_pkt_t *pkt); + +/*! \brief Begone you foul creature of the underworld. */ +void knot_pkt_free(knot_pkt_t *pkt); + +/*! + * \brief Reserve an arbitrary amount of space in the packet. + * + * \return KNOT_EOK + * \return KNOT_ERANGE if size can't be reserved + */ +int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size); + +/*! + * \brief Reclaim reserved size. + * + * \return KNOT_EOK + * \return KNOT_ERANGE if size can't be reclaimed + */ +int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size); + +/* + * Packet QUESTION accessors. + */ +static inline uint16_t knot_pkt_question_size(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return 0; + } + + return pkt->qname_size + 2 * sizeof(uint16_t); +} + +static inline knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return NULL; + } + + return pkt->wire + KNOT_WIRE_HEADER_SIZE; +} + +static inline uint16_t knot_pkt_qtype(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return 0; + } + + unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size; + return knot_wire_read_u16(pkt->wire + off); +} + +static inline uint16_t knot_pkt_qclass(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return 0; + } + + unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size + sizeof(uint16_t); + return knot_wire_read_u16(pkt->wire + off); +} + +/* + * Packet writing API. + */ + +/*! + * \brief Begin reading/writing packet section. + * + * \note You must proceed in the natural order (ANSWER, AUTHORITY, ADDITIONAL). + * + * \param pkt + * \param section_id + * \return KNOT_EOK or KNOT_EINVAL + */ +int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id); + +/*! + * \brief Put QUESTION in the packet. + * + * \note Since we support QD=1 only, QUESTION is a special type of packet section. + * \note Must not be used after putting RRsets into the packet. + * + * \param pkt + * \param qname + * \param qclass + * \param qtype + * \return KNOT_EOK or various errors + */ +int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, + uint16_t qclass, uint16_t qtype); + +/*! + * \brief Put RRSet into packet. + * + * \note See compr.h for description on how compression hints work. + * \note Available flags: PF_FREE, KNOT_PF_CHECKDUP, KNOT_PF_NOTRUNC + * + * \param pkt + * \param compr_hint Compression hint, see enum knot_compr_hint or absolute + * position. + * \param rr Given RRSet. + * \param rotate Rotate the RRSet order by this count. + * \param flags RRSet flags (set PF_FREE if you want RRSet to be freed + * with the packet). + * + * \return KNOT_EOK, KNOT_ESPACE, various errors + */ +int knot_pkt_put_rotate(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, + uint16_t rotate, uint16_t flags); + +/*! \brief Same as knot_pkt_put_rotate but without rrset rotation. */ +static inline int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, + const knot_rrset_t *rr, uint16_t flags) +{ + return knot_pkt_put_rotate(pkt, compr_hint, rr, 0, flags); +} + +/*! \brief Get description of the given packet section. */ +static inline const knot_pktsection_t *knot_pkt_section(const knot_pkt_t *pkt, + knot_section_t section_id) +{ + assert(pkt); + return &pkt->sections[section_id]; +} + +/*! \brief Get RRSet from the packet section. */ +static inline const knot_rrset_t *knot_pkt_rr(const knot_pktsection_t *section, + uint16_t i) +{ + assert(section); + return section->pkt->rr + section->pos + i; +} + +/*! \brief Get RRSet offset in the packet wire. */ +static inline uint16_t knot_pkt_rr_offset(const knot_pktsection_t *section, + uint16_t i) +{ + assert(section); + return section->pkt->rr_info[section->pos + i].pos; +} + +/* + * Packet parsing API. + */ + +/*! + * \brief Parse both packet question and payload. + * + * Parses both QUESTION and all packet sections, + * includes semantic checks over specific RRs (TSIG, OPT). + * + * \note If KNOT_PF_KEEPWIRE is set, TSIG RR is not stripped from the wire + * and is processed as any other RR. + * + * \param pkt Given packet. + * \param flags Parsing flags (allowed KNOT_PF_KEEPWIRE) + * + * \retval KNOT_EOK if success. + * \retval KNOT_ETRAIL if success but with some trailing data. + * \retval KNOT_EMALF and other errors. + */ +int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags); + +/*! + * \brief Parse packet header and a QUESTION section. + */ +int knot_pkt_parse_question(knot_pkt_t *pkt); + +/*! + * \brief Get packet extended RCODE. + * + * Extended RCODE is created by considering TSIG RCODE, EDNS RCODE and + * DNS Header RCODE. (See RFC 6895, Section 2.3). + * + * \param pkt Packet to get the response code from. + * + * \return Whole extended RCODE (0 if pkt == NULL). + */ +uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt); + +/*! + * \brief Get packet extended RCODE name. + * + * The packet parameter is important as the name depends on TSIG. + * + * \param pkt Packet to get the response code from. + * + * \return RCODE name (or empty string if not known). + */ +const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt); + +/*! + * \brief Checks if there is an OPT RR in the packet. + */ +static inline bool knot_pkt_has_edns(const knot_pkt_t *pkt) +{ + assert(pkt); + return pkt->opt_rr != NULL; +} + +/*! + * \brief Checks if TSIG is present. + */ +static inline bool knot_pkt_has_tsig(const knot_pkt_t *pkt) +{ + assert(pkt); + return pkt->tsig_rr != NULL; +} + +/*! + * \brief Checks if DO bit is set in the packet's OPT RR. + */ +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 Get specific EDNS option from a parsed packet. + */ +static inline uint8_t *knot_pkt_edns_option(const knot_pkt_t *pkt, uint16_t code) +{ + assert(pkt); + if (pkt->edns_opts != NULL && code <= KNOT_EDNS_MAX_OPTION_CODE) { + return pkt->edns_opts->ptr[code]; + } else { + return NULL; + } +} + +/*! + * \brief Computes a reasonable Padding data length for a given packet and opt RR. + * + * \param pkt DNS Packet prepared and otherwise ready to go, no OPT yet added. + * \param opt_rr OPT RR, not yet including padding. + * + * \return Required padding length or -1 if padding not required. + */ +static inline int knot_pkt_default_padding_size(const knot_pkt_t *pkt, + const knot_rrset_t *opt_rr) +{ + if (knot_wire_get_qr(pkt->wire)) { + return knot_edns_alignment_size(pkt->size, knot_rrset_size(opt_rr), + KNOT_EDNS_ALIGNMENT_RESPONSE_DEFAULT); + } else { + return knot_edns_alignment_size(pkt->size, knot_rrset_size(opt_rr), + KNOT_EDNS_ALIGNMENT_QUERY_DEFALT); + } +} + +static inline size_t knot_pkt_size(const knot_pkt_t *pkt) +{ + assert(pkt); + return pkt->size + (knot_pkt_has_tsig(pkt) ? pkt->tsig_wire.len : 0); +} + +/*! @} */ diff --git a/src/libknot/packet/rrset-wire.c b/src/libknot/packet/rrset-wire.c new file mode 100644 index 0000000..78171a5 --- /dev/null +++ b/src/libknot/packet/rrset-wire.c @@ -0,0 +1,723 @@ +/* Copyright (C) 2020 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 "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/packet/pkt.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/rrtype/naptr.h" +#include "libknot/rrtype/rrsig.h" +#include "libknot/rrtype/soa.h" +#include "contrib/macros.h" +#include "contrib/mempattern.h" +#include "contrib/tolower.h" +#include "contrib/wire_ctx.h" + +/*! + * \brief Get maximal size of a domain name in a wire with given capacity. + */ +static uint16_t dname_max(size_t wire_avail) +{ + return MIN(wire_avail, KNOT_DNAME_MAXLEN); +} + +/*! + * \brief Compares two domain name labels. + * + * \param label1 First label. + * \param label2 Second label (may be in upper-case). + * + * \retval true if the labels are identical + * \retval false if the labels are NOT identical + */ +static bool label_is_equal(const uint8_t *label1, const uint8_t *label2) +{ + assert(label1 && label2); + + if (*label1 != *label2) { + return false; + } + + uint8_t len = *label1; + for (uint8_t i = 1; i <= len; i++) { + if (label1[i] != knot_tolower(label2[i])) { + return false; + } + } + + return true; +} + +/*! + * Case insensitive comparison of two dnames in wire format. + * The second name may be compressed in a supplied wire. + */ +static bool dname_equal_wire(const knot_dname_t *d1, const knot_dname_t *d2, + const uint8_t *wire) +{ + assert(d1); + assert(d2); + + d2 = knot_wire_seek_label(d2, wire); + + while (*d1 != '\0' || *d2 != '\0') { + if (!label_is_equal(d1, d2)) { + return false; + } + d1 = knot_wire_next_label(d1, NULL); + d2 = knot_wire_next_label(d2, wire); + } + + return true; +} + +static uint16_t compr_get_ptr(knot_compr_t *compr, uint16_t hint) +{ + if (compr == NULL) { + return 0; + } + + return knot_compr_hint(compr->rrinfo, hint); +} + +static void compr_set_ptr(knot_compr_t *compr, uint16_t hint, + const uint8_t *written_at, uint16_t written_size) +{ + if (compr == NULL) { + return; + } + + assert(written_at >= compr->wire); + + uint16_t offset = written_at - compr->wire; + + knot_compr_hint_set(compr->rrinfo, hint, offset, written_size); +} + +static int write_rdata_fixed(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail, size_t size) +{ + assert(src && *src); + assert(src_avail); + assert(dst && *dst); + assert(dst_avail); + + // Check input/output buffer boundaries. + if (size > *src_avail) { + return KNOT_EMALF; + } + + if (size > *dst_avail) { + return KNOT_ESPACE; + } + + // Data binary copy. + memcpy(*dst, *src, size); + + // Update buffers. + *src += size; + *src_avail -= size; + + *dst += size; + *dst_avail -= size; + + return KNOT_EOK; +} + +static int write_rdata_naptr_header(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail) +{ + assert(src && *src); + assert(src_avail); + assert(dst && *dst); + assert(dst_avail); + + int ret = knot_naptr_header_size(*src, *src + *src_avail); + if (ret < 0) { + return ret; + } + + // Copy the data. + return write_rdata_fixed(src, src_avail, dst, dst_avail, ret); +} + +/*! \brief Helper for \ref compr_put_dname, writes label(s) with size checks. */ +#define WRITE_LABEL(dst, written, label, max, len) \ + if ((written) + (len) > (max)) { \ + return KNOT_ESPACE; \ + } else { \ + memcpy((dst) + (written), (label), (len)); \ + written += (len); \ + } + +/*! + * \brief Write compressed domain name to the destination wire. + * + * \param dname Name to be written. + * \param dst Destination wire. + * \param max Maximum number of bytes available. + * \param compr Compression context (NULL for no compression) + * \return Number of written bytes or an error. + */ +static int compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max, + knot_compr_t *compr) +{ + assert(dname && dst); + + // Write uncompressible names directly (zero label dname). + if (compr == NULL || *dname == '\0') { + return knot_dname_to_wire(dst, dname, max); + } + + // Get number of labels (should not be a zero label dname). + size_t name_labels = knot_dname_labels(dname, NULL); + assert(name_labels > 0); + + // Suffix must not be longer than whole name. + const knot_dname_t *suffix = compr->wire + compr->suffix.pos; + int suffix_labels = compr->suffix.labels; + while (suffix_labels > name_labels) { + suffix = knot_wire_next_label(suffix, compr->wire); + --suffix_labels; + } + + // Suffix is shorter than name, write labels until aligned. + uint8_t orig_labels = name_labels; + uint16_t written = 0; + while (name_labels > suffix_labels) { + WRITE_LABEL(dst, written, dname, max, (*dname + 1)); + dname = knot_wire_next_label(dname, NULL); + --name_labels; + } + + // Label count is now equal. + assert(name_labels == suffix_labels); + const knot_dname_t *match_begin = dname; + const knot_dname_t *compr_ptr = suffix; + while (dname[0] != '\0') { + // Next labels. + const knot_dname_t *next_dname = knot_wire_next_label(dname, NULL); + const knot_dname_t *next_suffix = knot_wire_next_label(suffix, compr->wire); + + // Two labels match, extend suffix length. + if (!label_is_equal(dname, suffix)) { + // If they don't match, write unmatched labels. + uint16_t mismatch_len = (dname - match_begin) + (*dname + 1); + WRITE_LABEL(dst, written, match_begin, max, mismatch_len); + // Start new potential match. + match_begin = next_dname; + compr_ptr = next_suffix; + } + + // Jump to next labels. + dname = next_dname; + suffix = next_suffix; + } + + // If match begins at the end of the name, write '\0' label. + if (match_begin == dname) { + WRITE_LABEL(dst, written, dname, max, 1); + } else { + // Match covers >0 labels, write out compression pointer. + if (written + sizeof(uint16_t) > max) { + return KNOT_ESPACE; + } + knot_wire_put_pointer(dst + written, compr_ptr - compr->wire); + written += sizeof(uint16_t); + } + + assert(dst >= compr->wire); + size_t wire_pos = dst - compr->wire; + assert(wire_pos < KNOT_WIRE_MAX_PKTSIZE); + + // Heuristics - expect similar names are grouped together. + if (written > sizeof(uint16_t) && wire_pos + written < KNOT_WIRE_PTR_MAX) { + compr->suffix.pos = wire_pos; + compr->suffix.labels = orig_labels; + } + + return written; +} + +#define WRITE_OWNER_CHECK(size, dst_avail) \ + if ((size) > *(dst_avail)) { \ + return KNOT_ESPACE; \ + } + +#define WRITE_OWNER_INCR(dst, dst_avail, size) \ + *(dst) += (size); \ + *(dst_avail) -= (size); + +static int write_owner(const knot_rrset_t *rrset, uint8_t **dst, size_t *dst_avail, + knot_compr_t *compr) +{ + assert(rrset); + assert(dst && *dst); + assert(dst_avail); + + // Check for zero label owner (don't compress). + uint16_t owner_pointer = 0; + if (*rrset->owner != '\0') { + owner_pointer = compr_get_ptr(compr, KNOT_COMPR_HINT_OWNER); + } + + // Write result. + if (owner_pointer > 0) { + WRITE_OWNER_CHECK(sizeof(uint16_t), dst_avail); + knot_wire_put_pointer(*dst, owner_pointer); + WRITE_OWNER_INCR(dst, dst_avail, sizeof(uint16_t)); + // Check for coincidence with previous RR set. + } else if (compr != NULL && compr->suffix.pos != 0 && *rrset->owner != '\0' && + dname_equal_wire(rrset->owner, compr->wire + compr->suffix.pos, + compr->wire)) { + WRITE_OWNER_CHECK(sizeof(uint16_t), dst_avail); + knot_wire_put_pointer(*dst, compr->suffix.pos); + compr_set_ptr(compr, KNOT_COMPR_HINT_OWNER, + compr->wire + compr->suffix.pos, + knot_dname_size(rrset->owner)); + WRITE_OWNER_INCR(dst, dst_avail, sizeof(uint16_t)); + } else { + if (compr != NULL) { + compr->suffix.pos = KNOT_WIRE_HEADER_SIZE; + compr->suffix.labels = + knot_dname_labels(compr->wire + compr->suffix.pos, + compr->wire); + } + // WRITE_OWNER_CHECK not needed, compr_put_dname has a check. + int written = compr_put_dname(rrset->owner, *dst, + dname_max(*dst_avail), compr); + if (written < 0) { + return written; + } + + compr_set_ptr(compr, KNOT_COMPR_HINT_OWNER, *dst, written); + WRITE_OWNER_INCR(dst, dst_avail, written); + } + + return KNOT_EOK; +} + +static int write_fixed_header(const knot_rrset_t *rrset, uint16_t rrset_index, + uint8_t **dst, size_t *dst_avail, uint16_t flags) +{ + assert(rrset); + assert(rrset_index < rrset->rrs.count); + assert(dst && *dst); + assert(dst_avail); + + // Write header. + wire_ctx_t write = wire_ctx_init(*dst, *dst_avail); + + wire_ctx_write_u16(&write, rrset->type); + wire_ctx_write_u16(&write, rrset->rclass); + + if ((flags & KNOT_PF_ORIGTTL) && rrset->type == KNOT_RRTYPE_RRSIG) { + const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index); + wire_ctx_write_u32(&write, knot_rrsig_original_ttl(rdata)); + } else if ((flags & KNOT_PF_SOAMINTTL) && rrset->type == KNOT_RRTYPE_SOA) { + const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index); + wire_ctx_write_u32(&write, MIN(knot_soa_minimum(rdata), rrset->ttl)); + } else { + wire_ctx_write_u32(&write, rrset->ttl); + } + + // Check write. + if (write.error != KNOT_EOK) { + return write.error; + } + + // Update buffer. + *dst = write.position; + *dst_avail = wire_ctx_available(&write); + + return KNOT_EOK; +} + +static int compress_rdata_dname(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail, + knot_compr_t *put_compr, knot_compr_t *compr, + uint16_t hint) +{ + assert(src && *src); + assert(src_avail); + assert(dst && *dst); + assert(dst_avail); + + // Source domain name. + const knot_dname_t *dname = *src; + size_t dname_size = knot_dname_size(dname); + + // Output domain name. + int written = compr_put_dname(dname, *dst, dname_max(*dst_avail), put_compr); + if (written < 0) { + return written; + } + + // Update compression hints. + if (compr_get_ptr(compr, hint) == 0) { + compr_set_ptr(compr, hint, *dst, written); + } + + // Update buffers. + *dst += written; + *dst_avail -= written; + + *src += dname_size; + *src_avail -= dname_size; + + return KNOT_EOK; +} + +static int rdata_traverse_write(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail, + const knot_rdata_descriptor_t *desc, + knot_compr_t *compr, uint16_t hint) +{ + for (const int *type = desc->block_types; *type != KNOT_RDATA_WF_END; type++) { + int ret; + knot_compr_t *put_compr = NULL; + switch (*type) { + case KNOT_RDATA_WF_COMPRESSIBLE_DNAME: + put_compr = compr; + // FALLTHROUGH + case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME: + case KNOT_RDATA_WF_FIXED_DNAME: + ret = compress_rdata_dname(src, src_avail, dst, dst_avail, + put_compr, compr, hint); + break; + case KNOT_RDATA_WF_NAPTR_HEADER: + ret = write_rdata_naptr_header(src, src_avail, dst, dst_avail); + break; + case KNOT_RDATA_WF_REMAINDER: + ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *src_avail); + break; + default: + // Fixed size block. + assert(*type > 0); + ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *type); + break; + } + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +static int write_rdata(const knot_rrset_t *rrset, uint16_t rrset_index, + uint8_t **dst, size_t *dst_avail, knot_compr_t *compr) +{ + assert(rrset); + assert(rrset_index < rrset->rrs.count); + assert(dst && *dst); + assert(dst_avail); + + const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index); + + // Reserve space for RDLENGTH. + if (sizeof(uint16_t) > *dst_avail) { + return KNOT_ESPACE; + } + + uint8_t *wire_rdlength = *dst; + *dst += sizeof(uint16_t); + *dst_avail -= sizeof(uint16_t); + uint8_t *wire_rdata_begin = *dst; + + // Write RDATA. + const uint8_t *src = rdata->data; + size_t src_avail = rdata->len; + if (src_avail > 0) { + // Only write non-empty data. + const knot_rdata_descriptor_t *desc = + knot_get_rdata_descriptor(rrset->type); + int ret = rdata_traverse_write(&src, &src_avail, dst, dst_avail, + desc, compr, KNOT_COMPR_HINT_RDATA + rrset_index); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Check for trailing data in the message. + if (src_avail > 0) { + return KNOT_EMALF; + } + + // Write final RDLENGTH. + size_t rdlength = *dst - wire_rdata_begin; + knot_wire_write_u16(wire_rdlength, rdlength); + + return KNOT_EOK; +} + +static int write_rr(const knot_rrset_t *rrset, uint16_t rrset_index, uint8_t **dst, + size_t *dst_avail, knot_compr_t *compr, uint16_t flags) +{ + int ret = write_owner(rrset, dst, dst_avail, compr); + if (ret != KNOT_EOK) { + return ret; + } + + ret = write_fixed_header(rrset, rrset_index, dst, dst_avail, flags); + if (ret != KNOT_EOK) { + return ret; + } + + return write_rdata(rrset, rrset_index, dst, dst_avail, compr); +} + +_public_ +int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire, + uint16_t max_size, uint16_t rotate, + knot_compr_t *compr, uint16_t flags) +{ + if (rrset == NULL || wire == NULL) { + return KNOT_EINVAL; + } + if (rrset->rrs.count == 0) { + return 0; + } + if (rotate != 0) { + rotate %= rrset->rrs.count; + } + + uint8_t *write = wire; + size_t capacity = max_size; + + uint16_t count = rrset->rrs.count; + for (uint16_t i = rotate; i < count + rotate; i++) { + uint16_t pos = (i < count) ? i : (i - count); + int ret = write_rr(rrset, pos, &write, &capacity, compr, flags); + if (ret != KNOT_EOK) { + return ret; + } + } + + return write - wire; +} + +static int parse_header(const uint8_t *wire, size_t *pos, size_t pkt_size, + knot_mm_t *mm, knot_rrset_t *rrset, uint16_t *rdlen) +{ + assert(wire); + assert(pos); + assert(rrset); + assert(rdlen); + + wire_ctx_t src = wire_ctx_init_const(wire, pkt_size); + wire_ctx_set_offset(&src, *pos); + + int compr_size = knot_dname_wire_check(src.position, wire + pkt_size, wire); + if (compr_size <= 0) { + return KNOT_EMALF; + } + + knot_dname_storage_t buff; + int decompr_size = knot_dname_unpack(buff, src.position, sizeof(buff), wire); + if (decompr_size <= 0) { + return KNOT_EMALF; + } + + knot_dname_t *owner = mm_alloc(mm, decompr_size); + if (owner == NULL) { + return KNOT_ENOMEM; + } + memcpy(owner, buff, decompr_size); + wire_ctx_skip(&src, compr_size); + + uint16_t type = wire_ctx_read_u16(&src); + uint16_t rclass = wire_ctx_read_u16(&src); + uint32_t ttl = wire_ctx_read_u32(&src); + *rdlen = wire_ctx_read_u16(&src); + + if (src.error != KNOT_EOK) { + knot_dname_free(owner, mm); + return KNOT_EMALF; + } + + if (wire_ctx_available(&src) < *rdlen) { + knot_dname_free(owner, mm); + return KNOT_EMALF; + } + + *pos = wire_ctx_offset(&src); + + knot_rrset_init(rrset, owner, type, rclass, ttl); + + return KNOT_EOK; +} + +static int decompress_rdata_dname(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail, + const uint8_t *pkt_wire) +{ + assert(src && *src); + assert(src_avail); + assert(dst && *dst); + assert(dst_avail); + + int compr_size = knot_dname_wire_check(*src, *src + *src_avail, pkt_wire); + if (compr_size <= 0) { + return compr_size; + } + + int decompr_size = knot_dname_unpack(*dst, *src, *dst_avail, pkt_wire); + if (decompr_size <= 0) { + return decompr_size; + } + + // Update buffers. + *dst += decompr_size; + *dst_avail -= decompr_size; + + *src += compr_size; + *src_avail -= compr_size; + + return KNOT_EOK; +} + +static int rdata_traverse_parse(const uint8_t **src, size_t *src_avail, + uint8_t **dst, size_t *dst_avail, + const knot_rdata_descriptor_t *desc, + const uint8_t *pkt_wire) +{ + for (const int *type = desc->block_types; *type != KNOT_RDATA_WF_END; type++) { + int ret; + switch (*type) { + case KNOT_RDATA_WF_COMPRESSIBLE_DNAME: + case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME: + case KNOT_RDATA_WF_FIXED_DNAME: + ret = decompress_rdata_dname(src, src_avail, dst, dst_avail, + pkt_wire); + break; + case KNOT_RDATA_WF_NAPTR_HEADER: + ret = write_rdata_naptr_header(src, src_avail, dst, dst_avail); + break; + case KNOT_RDATA_WF_REMAINDER: + ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *src_avail); + break; + default: + /* Fixed size block */ + assert(*type > 0); + ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *type); + break; + } + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +static bool allow_zero_rdata(const knot_rrset_t *rr, + const knot_rdata_descriptor_t *desc) +{ + return rr->rclass != KNOT_CLASS_IN || // NONE and ANY for DDNS + rr->type == KNOT_RRTYPE_APL || // APL RR type + desc->type_name == NULL; // Unknown RR type +} + +static int parse_rdata(const uint8_t *pkt_wire, size_t *pos, size_t pkt_size, + knot_mm_t *mm, uint16_t rdlength, knot_rrset_t *rrset) +{ + assert(pkt_wire); + assert(pos); + assert(rrset); + + const knot_rdata_descriptor_t *desc = knot_get_rdata_descriptor(rrset->type); + if (desc->type_name == NULL) { + desc = knot_get_obsolete_rdata_descriptor(rrset->type); + } + + if (rdlength == 0) { + if (allow_zero_rdata(rrset, desc)) { + return knot_rrset_add_rdata(rrset, NULL, 0, mm); + } else { + return KNOT_EMALF; + } + } else if (pkt_size - *pos < rdlength) { + return KNOT_EMALF; + } + + // Buffer for parsed rdata (decompression extends rdata length). + const size_t max_rdata_len = UINT16_MAX; + uint8_t buf[knot_rdata_size(max_rdata_len)]; + knot_rdata_t *rdata = (knot_rdata_t *)buf; + + const uint8_t *src = pkt_wire + *pos; + size_t src_avail = rdlength; + uint8_t *dst = rdata->data; + size_t dst_avail = max_rdata_len; + + // Parse RDATA. + int ret = rdata_traverse_parse(&src, &src_avail, &dst, &dst_avail, desc, pkt_wire); + if (ret != KNOT_EOK) { + return KNOT_EMALF; + } + + // Check for trailing data. + size_t real_len = max_rdata_len - dst_avail; + if (real_len < rdlength) { + return KNOT_EMALF; + } + rdata->len = real_len; + + ret = knot_rdataset_add(&rrset->rrs, rdata, mm); + if (ret != KNOT_EOK) { + return ret; + } + + // Update position pointer. + *pos += rdlength; + + return KNOT_EOK; +} + +_public_ +int knot_rrset_rr_from_wire(const uint8_t *wire, size_t *pos, size_t max_size, + knot_rrset_t *rrset, knot_mm_t *mm, bool canonical) +{ + if (wire == NULL || pos == NULL || *pos > max_size || rrset == NULL) { + return KNOT_EINVAL; + } + + uint16_t rdlen = 0; + int ret = parse_header(wire, pos, max_size, mm, rrset, &rdlen); + if (ret != KNOT_EOK) { + return ret; + } + + ret = parse_rdata(wire, pos, max_size, mm, rdlen, rrset); + if (ret != KNOT_EOK) { + knot_rrset_clear(rrset, mm); + return ret; + } + + // Convert RR to the canonical format. + if (canonical) { + ret = knot_rrset_rr_to_canonical(rrset); + if (ret != KNOT_EOK) { + knot_rrset_clear(rrset, mm); + } + } + + return KNOT_EOK; +} diff --git a/src/libknot/packet/rrset-wire.h b/src/libknot/packet/rrset-wire.h new file mode 100644 index 0000000..3be0cba --- /dev/null +++ b/src/libknot/packet/rrset-wire.h @@ -0,0 +1,69 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief RRSet from/to wire conversion functions. + * + * \addtogroup wire + * @{ + */ + +#pragma once + +#include "libknot/rrset.h" +#include "libknot/packet/compr.h" + +/*! + * \brief Write RR Set content to a wire. + * + * \param rrset RRSet to be converted. + * \param wire Output wire buffer. + * \param max_size Capacity of wire buffer. + * \param rotate Rotate the RR order by this count. + * \param compr Compression context. + * \param flags Flags; currently only KNOT_PF_TTL_ORIG is accepted. + * + * \return Output size, negative number on error (KNOT_E*). + */ +int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire, + uint16_t max_size, uint16_t rotate, + knot_compr_t *compr, uint16_t flags); + +/*! \brief Same as knot_rrset_to_wire_extra but without rrset rotation and flags. */ +static inline int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, + uint16_t max_size, knot_compr_t *compr) +{ + return knot_rrset_to_wire_extra(rrset, wire, max_size, 0, compr, 0); +} + +/*! +* \brief Creates one RR from wire, stores it into \a rrset. +* +* \param wire Source wire (the whole packet). +* \param pos Position in \a wire where to start parsing. +* \param max_size Total size of data in \a wire (size of the packet). +* \param rrset Destination RRSet. +* \param mm Memory context. +* \param canonical Convert rrset to canonical format indication. +* +* \return KNOT_E* +*/ +int knot_rrset_rr_from_wire(const uint8_t *wire, size_t *pos, size_t max_size, + knot_rrset_t *rrset, knot_mm_t *mm, bool canonical); + +/*! @} */ diff --git a/src/libknot/packet/wire.h b/src/libknot/packet/wire.h new file mode 100644 index 0000000..698ac3d --- /dev/null +++ b/src/libknot/packet/wire.h @@ -0,0 +1,1045 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \brief Functions for manipulating and parsing raw data in DNS packets. + * + * \addtogroup wire + * @{ + */ + +#pragma once + +#include <assert.h> +#include <stdint.h> + +#include "libknot/attribute.h" +#include "libknot/wire.h" + +/*! \brief Offset of DNS header fields in wireformat. */ +enum knot_wire_offsets { + KNOT_WIRE_OFFSET_ID = 0, + KNOT_WIRE_OFFSET_FLAGS1 = 2, + KNOT_WIRE_OFFSET_FLAGS2 = 3, + KNOT_WIRE_OFFSET_QDCOUNT = 4, + KNOT_WIRE_OFFSET_ANCOUNT = 6, + KNOT_WIRE_OFFSET_NSCOUNT = 8, + KNOT_WIRE_OFFSET_ARCOUNT = 10 +}; + +/*! \brief Minimum size for some parts of the DNS packet. */ +enum knot_wire_sizes { + KNOT_WIRE_HEADER_SIZE = 12, + KNOT_WIRE_QUESTION_MIN_SIZE = 5, + KNOT_WIRE_RR_MIN_SIZE = 11, + KNOT_WIRE_MIN_PKTSIZE = 512, + KNOT_WIRE_MAX_PKTSIZE = 65535, + KNOT_WIRE_MAX_PAYLOAD = KNOT_WIRE_MAX_PKTSIZE + - KNOT_WIRE_HEADER_SIZE + - KNOT_WIRE_QUESTION_MIN_SIZE +}; + +/* + * Packet header manipulation functions. + */ + +/*! + * \brief Returns the ID from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return DNS packet ID. + */ +static inline uint16_t knot_wire_get_id(const uint8_t *packet) +{ + assert(packet); + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ID); +} + +/*! + * \brief Sets the ID to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param id DNS packet ID. + */ +static inline void knot_wire_set_id(uint8_t *packet, uint16_t id) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ID, id); +} + +/*! + * \brief Returns the first byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return First byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags1(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1); +} + +/*! + * \brief Sets the first byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags1 First byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags1(uint8_t *packet, uint8_t flags1) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) = flags1; +} + +/*! + * \brief Returns the second byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Second byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags2(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2); +} + +/*! + * \brief Sets the second byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags2 Second byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags2(uint8_t *packet, uint8_t flags2) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) = flags2; +} + +/*! + * \brief Returns the QDCOUNT (count of Question entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return QDCOUNT (count of Question entries in the packet). + */ +static inline uint16_t knot_wire_get_qdcount(const uint8_t *packet) +{ + assert(packet); + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT); +} + +/*! + * \brief Sets the QDCOUNT (count of Question entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param qdcount QDCOUNT (count of Question entries in the packet). + */ +static inline void knot_wire_set_qdcount(uint8_t *packet, uint16_t qdcount) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT, qdcount); +} + +/*! + * \brief Adds to QDCOUNT. + */ +static inline void knot_wire_add_qdcount(uint8_t *packet, int16_t n) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT, + knot_wire_get_qdcount(packet) + n); +} + +/*! + * \brief Returns the ANCOUNT (count of Answer entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ANCOUNT (count of Answer entries in the packet). + */ +static inline uint16_t knot_wire_get_ancount(const uint8_t *packet) +{ + assert(packet); + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT); +} + +/*! + * \brief Sets the ANCOUNT (count of Answer entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param ancount ANCOUNT (count of Answer entries in the packet). + */ +static inline void knot_wire_set_ancount(uint8_t *packet, uint16_t ancount) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT, ancount); +} + +/*! + * \brief Adds to ANCOUNT. + */ +static inline void knot_wire_add_ancount(uint8_t *packet, int16_t n) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT, + knot_wire_get_ancount(packet) + n); +} + +/*! + * \brief Returns the NSCOUNT (count of Authority entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return NSCOUNT (count of Authority entries in the packet). + */ +static inline uint16_t knot_wire_get_nscount(const uint8_t *packet) +{ + assert(packet); + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT); +} + +/*! + * \brief Sets the NSCOUNT (count of Authority entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param nscount NSCOUNT (count of Authority entries in the packet). + */ +static inline void knot_wire_set_nscount(uint8_t *packet, uint16_t nscount) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT, nscount); +} + +/*! + * \brief Adds to NSCOUNT. + */ +static inline void knot_wire_add_nscount(uint8_t *packet, int16_t n) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT, + knot_wire_get_nscount(packet) + n); +} + +/*! + * \brief Returns the ARCOUNT (count of Additional entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ARCOUNT (count of Additional entries in the packet). + */ +static inline uint16_t knot_wire_get_arcount(const uint8_t *packet) +{ + assert(packet); + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT); +} + +/*! + * \brief Sets the ARCOUNT (count of Additional entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param arcount ARCOUNT (count of Additional entries in the packet). + */ +static inline void knot_wire_set_arcount(uint8_t *packet, uint16_t arcount) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT, arcount); +} + +/*! + * \brief Adds to ARCOUNT. + */ +static inline void knot_wire_add_arcount(uint8_t *packet, int16_t n) +{ + assert(packet); + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT, + knot_wire_get_arcount(packet) + n); +} + +/* + * Packet header flags manipulation functions. + */ +/*! \brief Constants for DNS header flags in the first flags byte. */ +enum knot_wire_flags1_consts { + KNOT_WIRE_RD_MASK = (uint8_t)0x01U, /*!< RD bit mask. */ + KNOT_WIRE_RD_SHIFT = 0, /*!< RD bit shift. */ + KNOT_WIRE_TC_MASK = (uint8_t)0x02U, /*!< TC bit mask. */ + KNOT_WIRE_TC_SHIFT = 1, /*!< TC bit shift. */ + KNOT_WIRE_AA_MASK = (uint8_t)0x04U, /*!< AA bit mask. */ + KNOT_WIRE_AA_SHIFT = 2, /*!< AA bit shift. */ + KNOT_WIRE_OPCODE_MASK = (uint8_t)0x78U, /*!< OPCODE mask. */ + KNOT_WIRE_OPCODE_SHIFT = 3, /*!< OPCODE shift. */ + KNOT_WIRE_QR_MASK = (uint8_t)0x80U, /*!< QR bit mask. */ + KNOT_WIRE_QR_SHIFT = 7 /*!< QR bit shift. */ +}; + +/*! \brief Constants for DNS header flags in the second flags byte. */ +enum knot_wire_flags2_consts { + KNOT_WIRE_RCODE_MASK = (uint8_t)0x0fU, /*!< RCODE mask. */ + KNOT_WIRE_RCODE_SHIFT = 0, /*!< RCODE shift. */ + KNOT_WIRE_CD_MASK = (uint8_t)0x10U, /*!< CD bit mask. */ + KNOT_WIRE_CD_SHIFT = 4, /*!< CD bit shift. */ + KNOT_WIRE_AD_MASK = (uint8_t)0x20U, /*!< AD bit mask. */ + KNOT_WIRE_AD_SHIFT = 5, /*!< AD bit shift. */ + KNOT_WIRE_Z_MASK = (uint8_t)0x40U, /*!< Zero bit mask. */ + KNOT_WIRE_Z_SHIFT = 6, /*!< Zero bit shift. */ + KNOT_WIRE_RA_MASK = (uint8_t)0x80U, /*!< RA bit mask. */ + KNOT_WIRE_RA_SHIFT = 7 /*!< RA bit shift. */ +}; + +/* + * Functions for getting / setting / clearing flags and codes directly in packet + */ + +/*! + * \brief Returns the RD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_rd(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_rd(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_rd(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the TC bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_tc(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_tc(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_tc(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_aa(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_aa(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_aa(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return OPCODE of the packet. + */ +static inline uint8_t knot_wire_get_opcode(const uint8_t *packet) +{ + assert(packet); + return (*(packet + KNOT_WIRE_OFFSET_FLAGS1) + & KNOT_WIRE_OPCODE_MASK) >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_set_opcode(uint8_t *packet, short opcode) +{ + assert(packet); + uint8_t *flags1 = packet + KNOT_WIRE_OFFSET_FLAGS1; + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Nonzero for responses and zero for queries. + */ +static inline uint8_t knot_wire_get_qr(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_qr(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_qr(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return RCODE of the packet. + */ +static inline uint8_t knot_wire_get_rcode(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) + & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param rcode RCODE to set. + */ +static inline void knot_wire_set_rcode(uint8_t *packet, short rcode) +{ + assert(packet); + uint8_t *flags2 = packet + KNOT_WIRE_OFFSET_FLAGS2; + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the CD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_cd(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_cd(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_cd(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ad(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ad(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ad(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the Zero bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_z(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_z(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_z(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ra(const uint8_t *packet) +{ + assert(packet); + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ra(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ra(uint8_t *packet) +{ + assert(packet); + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Functions for getting / setting / clearing flags in flags variable + */ + +/*! + * \brief Returns the RD bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the RD bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_rd(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_rd(uint8_t *flags1) +{ + assert(flags1); + *flags1 |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_rd(uint8_t *flags1) +{ + assert(flags1); + *flags1 &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the TC bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_tc(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_tc(uint8_t *flags1) +{ + assert(flags1); + *flags1 |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_tc(uint8_t *flags1) +{ + assert(flags1); + *flags1 &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the AA bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_aa(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_aa(uint8_t *flags1) +{ + assert(flags1); + *flags1 |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_aa(uint8_t *flags1) +{ + assert(flags1); + *flags1 &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return OPCODE + */ +static inline uint8_t knot_wire_flags_get_opcode(uint8_t flags1) +{ + return (flags1 & KNOT_WIRE_OPCODE_MASK) + >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_flags_set_opcode(uint8_t *flags1, short opcode) +{ + assert(flags1); + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the QR bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_qr(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_qr(uint8_t *flags1) +{ + assert(flags1); + *flags1 |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_qr(uint8_t *flags1) +{ + assert(flags1); + *flags1 &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from the second byte of flags. + * + * \param flags2 First byte of DNS header flags. + * + * \return RCODE + */ +static inline uint8_t knot_wire_flags_get_rcode(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * \param rcode RCODE to set. + */ +static inline void knot_wire_flags_set_rcode(uint8_t *flags2, short rcode) +{ + assert(flags2); + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the CD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_cd(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_cd(uint8_t *flags2) +{ + assert(flags2); + *flags2 |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_cd(uint8_t *flags2) +{ + assert(flags2); + *flags2 &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the AD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ad(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ad(uint8_t *flags2) +{ + assert(flags2); + *flags2 |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ad(uint8_t *flags2) +{ + assert(flags2); + *flags2 &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the Zero bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_z(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_z(uint8_t *flags2) +{ + assert(flags2); + *flags2 |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_z(uint8_t *flags2) +{ + assert(flags2); + *flags2 &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the RA bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ra(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ra(uint8_t *flags2) +{ + assert(flags2); + *flags2 |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ra(uint8_t *flags2) +{ + assert(flags2); + *flags2 &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Pointer manipulation + */ + +enum knot_wire_pointer_consts { + /*! \brief DNS packet pointer designation (first two bits set to 1). */ + KNOT_WIRE_PTR = (uint8_t)0xC0, + /*! \brief DNS packet minimal pointer (KNOT_WIRE_PTR + 1 zero byte). */ + KNOT_WIRE_PTR_BASE = (uint16_t)0xC000, + /*! \brief DNS packet maximal offset (KNOT_WIRE_BASE complement). */ + KNOT_WIRE_PTR_MAX = (uint16_t)0x3FFF +}; + +static inline int knot_wire_is_pointer(const uint8_t *pos) +{ + return pos && ((pos[0] & KNOT_WIRE_PTR) == KNOT_WIRE_PTR); +} + +/*! + * \brief Creates a DNS packet pointer and stores it in wire format. + * + * \param pos Position where tu put the pointer. + * \param ptr Relative position of the item to which the pointer should point in + * the wire format of the packet. + */ +static inline void knot_wire_put_pointer(uint8_t *pos, uint16_t ptr) +{ + knot_wire_write_u16(pos, ptr); // Write pointer offset. + assert((pos[0] & KNOT_WIRE_PTR) == 0); // Check for maximal offset. + pos[0] |= KNOT_WIRE_PTR; // Add pointer mark. +} + +static inline uint16_t knot_wire_get_pointer(const uint8_t *pos) +{ + assert(knot_wire_is_pointer(pos)); // Check pointer. + return (knot_wire_read_u16(pos) - KNOT_WIRE_PTR_BASE); // Return offset. +} + +_pure_ _mustcheck_ +static inline const uint8_t *knot_wire_seek_label(const uint8_t *lp, const uint8_t *wire) +{ + while (knot_wire_is_pointer(lp)) { + if (!wire) + return NULL; + lp = wire + knot_wire_get_pointer(lp); + } + return lp; +} + +_pure_ _mustcheck_ +static inline const uint8_t *knot_wire_next_label(const uint8_t *lp, const uint8_t *wire) +{ + if (!lp || !lp[0]) /* No label after final label. */ + return NULL; + return knot_wire_seek_label(lp + (lp[0] + sizeof(uint8_t)), wire); +} + +/*! @} */ diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h new file mode 100644 index 0000000..1891f91 --- /dev/null +++ b/src/libknot/rdata.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \brief API for manipulating rdata. + * + * \addtogroup rr + * @{ + */ + +#pragma once + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +/*!< \brief Maximum rdata length. */ +#define KNOT_RDATA_MAXLEN 65535 + +/*! + * \brief Structure holding single rdata payload. + */ +typedef struct { + uint16_t len; + uint8_t data[]; +} knot_rdata_t; + +/*! + * \brief Inits rdata structure. + * + * \param rdata Rdata structure to be initialized. At least knot_rdata_size bytes + * must fit into it! + * \param len Rdata length. + * \param data Rdata itself. + */ +inline static void knot_rdata_init(knot_rdata_t *rdata, uint16_t len, const uint8_t *data) +{ + assert(rdata); + rdata->len = len; + if (rdata->len > 0) { + assert(data); + memcpy(rdata->data, data, len); + if (len & 1) { // Initialize possible padding to mute analytical tools. + rdata->data[len] = 0; + } + } +} + +/*! + * \brief Returns actual size of the rdata structure for given rdata length. + * + * \param len Rdata length. + * + * \return Actual structure size. + */ +inline static size_t knot_rdata_size(uint16_t len) +{ + return sizeof(uint16_t) + len + (len & 1); +} + +/*! + * \brief Canonical comparison of two rdata structures. + * + * \param rdata1 First rdata to compare. + * \param rdata2 Second rdata to compare. + * + * \retval = 0 if rdata1 == rdata2. + * \retval < 0 if rdata1 < rdata2. + * \retval > 0 if rdata1 > rdata2. + */ +inline static int knot_rdata_cmp(const knot_rdata_t *rdata1, const knot_rdata_t *rdata2) +{ + assert(rdata1); + assert(rdata2); + + size_t common_len = (rdata1->len <= rdata2->len) ? rdata1->len : rdata2->len; + + int cmp = memcmp(rdata1->data, rdata2->data, common_len); + if (cmp == 0 && rdata1->len != rdata2->len) { + cmp = rdata1->len < rdata2->len ? -1 : 1; + } + return cmp; +} + +/*! @} */ diff --git a/src/libknot/rdataset.c b/src/libknot/rdataset.c new file mode 100644 index 0000000..19c4207 --- /dev/null +++ b/src/libknot/rdataset.c @@ -0,0 +1,321 @@ +/* Copyright (C) 2019 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 <string.h> +#include <stdlib.h> + +#include "libknot/attribute.h" +#include "libknot/rdataset.h" +#include "contrib/mempattern.h" + +static knot_rdata_t *rr_seek(const knot_rdataset_t *rrs, uint16_t pos) +{ + assert(rrs); + assert(0 < rrs->count); + assert(pos < rrs->count); + + uint8_t *raw = (uint8_t *)(rrs->rdata); + for (uint16_t i = 0; i < pos; ++i) { + raw += knot_rdata_size(((knot_rdata_t *)raw)->len); + } + + return (knot_rdata_t *)raw; +} + +static int find_rr_pos(const knot_rdataset_t *rrs, const knot_rdata_t *rr) +{ + knot_rdata_t *search_rr = rrs->rdata; + for (uint16_t i = 0; i < rrs->count; ++i) { + if (knot_rdata_cmp(rr, search_rr) == 0) { + return i; + } + search_rr = knot_rdataset_next(search_rr); + } + + return KNOT_ENOENT; +} + +static int add_rr_at(knot_rdataset_t *rrs, const knot_rdata_t *rr, knot_rdata_t *ins_pos, + knot_mm_t *mm) +{ + assert(rrs); + assert(rr); + const size_t ins_offset = (uint8_t *)ins_pos - (uint8_t *)rrs->rdata; + assert(ins_offset <= rrs->size); + + if (rrs->count == UINT16_MAX) { + return KNOT_ESPACE; + } else if (rrs->size > UINT32_MAX - knot_rdata_size(UINT16_MAX)) { + return KNOT_ESPACE; + } + + const size_t rr_size = knot_rdata_size(rr->len); + + // Realloc RDATA. + knot_rdata_t *tmp = mm_realloc(mm, rrs->rdata, rrs->size + rr_size, + rrs->size); + if (tmp == NULL) { + return KNOT_ENOMEM; + } else { + rrs->rdata = tmp; + } + + uint8_t *ins_pos_raw = (uint8_t *)rrs->rdata + ins_offset; + // RDATA may have to be rearanged. Moving zero-length region is OK. + memmove(ins_pos_raw + rr_size, ins_pos_raw, rrs->size - ins_offset); + + // Set new RDATA. + knot_rdata_init((knot_rdata_t *)ins_pos_raw, rr->len, rr->data); + rrs->count++; + rrs->size += rr_size; + + return KNOT_EOK; +} + +static int remove_rr_at(knot_rdataset_t *rrs, uint16_t pos, knot_mm_t *mm) +{ + assert(rrs); + assert(0 < rrs->count); + assert(pos < rrs->count); + + knot_rdata_t *old_rr = rr_seek(rrs, pos); + knot_rdata_t *last_rr = rr_seek(rrs, rrs->count - 1); + + size_t old_size = knot_rdata_size(old_rr->len); + + // Move RDATA. + uint8_t *old_threshold = (uint8_t *)old_rr + old_size; + uint8_t *last_threshold = (uint8_t *)last_rr + knot_rdata_size(last_rr->len); + assert(old_threshold <= last_threshold); + memmove(old_rr, old_threshold, last_threshold - old_threshold); + + if (rrs->count > 1) { + // Realloc RDATA. + knot_rdata_t *tmp = mm_realloc(mm, rrs->rdata, rrs->size - old_size, + rrs->size); + if (tmp == NULL) { + return KNOT_ENOMEM; + } else { + rrs->rdata = tmp; + } + } else { + // Free RDATA. + mm_free(mm, rrs->rdata); + rrs->rdata = NULL; + } + rrs->count--; + rrs->size -= old_size; + + return KNOT_EOK; +} + +_public_ +void knot_rdataset_clear(knot_rdataset_t *rrs, knot_mm_t *mm) +{ + if (rrs == NULL) { + return; + } + + mm_free(mm, rrs->rdata); + knot_rdataset_init(rrs); +} + +_public_ +int knot_rdataset_copy(knot_rdataset_t *dst, const knot_rdataset_t *src, knot_mm_t *mm) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + dst->count = src->count; + dst->size = src->size; + + if (src->count > 0) { + assert(src->rdata != NULL); + dst->rdata = mm_alloc(mm, src->size); + if (dst->rdata == NULL) { + return KNOT_ENOMEM; + } + memcpy(dst->rdata, src->rdata, src->size); + } else { + assert(src->size == 0); + dst->rdata = NULL; + } + + return KNOT_EOK; +} + +_public_ +knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *rrs, uint16_t pos) +{ + if (rrs == NULL || rrs->count == 0 || pos >= rrs->count) { + return NULL; + } + + return rr_seek(rrs, pos); +} + +_public_ +int knot_rdataset_add(knot_rdataset_t *rrs, const knot_rdata_t *rr, knot_mm_t *mm) +{ + if (rrs == NULL || rr == NULL) { + return KNOT_EINVAL; + } + + // Optimize a little for insertion at the end, for larger RRsets. + if (rrs->count > 4) { + knot_rdata_t *last = rr_seek(rrs, rrs->count - 1); + if (knot_rdata_cmp(last, rr) < 0) { + return add_rr_at(rrs, rr, knot_rdataset_next(last), mm); + } + } + + // Look for the right place to insert. + knot_rdata_t *ins_pos = rrs->rdata; + for (int i = 0; i < rrs->count; ++i, ins_pos = knot_rdataset_next(ins_pos)) { + int cmp = knot_rdata_cmp(ins_pos, rr); + if (cmp == 0) { + // Duplicate - no need to add this RR. + return KNOT_EOK; + } else if (cmp > 0) { + // Found position to insert. + return add_rr_at(rrs, rr, ins_pos, mm); + } + } + + assert((uint8_t *)rrs->rdata + rrs->size == (uint8_t *)ins_pos); + + // If flow gets here, it means that we should insert at the current position (append). + return add_rr_at(rrs, rr, ins_pos, mm); +} + +_public_ +bool knot_rdataset_eq(const knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2) +{ + if (rrs1 == NULL || rrs2 == NULL || rrs1->count != rrs2->count) { + return false; + } + + knot_rdata_t *rr1 = rrs1->rdata; + knot_rdata_t *rr2 = rrs2->rdata; + for (uint16_t i = 0; i < rrs1->count; ++i) { + if (knot_rdata_cmp(rr1, rr2) != 0) { + return false; + } + rr1 = knot_rdataset_next(rr1); + rr2 = knot_rdataset_next(rr2); + } + + return true; +} + +_public_ +bool knot_rdataset_member(const knot_rdataset_t *rrs, const knot_rdata_t *rr) +{ + if (rrs == NULL) { + return false; + } + + knot_rdata_t *cmp_rr = rrs->rdata; + for (uint16_t i = 0; i < rrs->count; ++i) { + int cmp = knot_rdata_cmp(cmp_rr, rr); + if (cmp == 0) { + // Match. + return true; + } else if (cmp > 0) { + // 'Greater' RR present, no need to continue. + return false; + } + cmp_rr = knot_rdataset_next(cmp_rr); + } + + return false; +} + +_public_ +int knot_rdataset_merge(knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2, + knot_mm_t *mm) +{ + if (rrs1 == NULL || rrs2 == NULL) { + return KNOT_EINVAL; + } + + knot_rdata_t *rr2 = rrs2->rdata; + for (uint16_t i = 0; i < rrs2->count; ++i) { + int ret = knot_rdataset_add(rrs1, rr2, mm); + if (ret != KNOT_EOK) { + return ret; + } + rr2 = knot_rdataset_next(rr2); + } + + return KNOT_EOK; +} + +_public_ +int knot_rdataset_intersect(const knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2, + knot_rdataset_t *out, knot_mm_t *mm) +{ + if (rrs1 == NULL || rrs2 == NULL || out == NULL) { + return KNOT_EINVAL; + } + + knot_rdataset_init(out); + knot_rdata_t *rr1 = rrs1->rdata; + for (uint16_t i = 0; i < rrs1->count; ++i) { + if (knot_rdataset_member(rrs2, rr1)) { + // Add RR into output intersection RRSet. + int ret = knot_rdataset_add(out, rr1, mm); + if (ret != KNOT_EOK) { + knot_rdataset_clear(out, mm); + return ret; + } + } + rr1 = knot_rdataset_next(rr1); + } + + return KNOT_EOK; +} + +_public_ +int knot_rdataset_subtract(knot_rdataset_t *from, const knot_rdataset_t *what, + knot_mm_t *mm) +{ + if (from == NULL || what == NULL) { + return KNOT_EINVAL; + } + + if (from->rdata == what->rdata) { + knot_rdataset_clear(from, mm); + knot_rdataset_init((knot_rdataset_t *) what); + return KNOT_EOK; + } + + knot_rdata_t *to_remove = what->rdata; + for (uint16_t i = 0; i < what->count; ++i) { + int pos_to_remove = find_rr_pos(from, to_remove); + if (pos_to_remove >= 0) { + int ret = remove_rr_at(from, pos_to_remove, mm); + if (ret != KNOT_EOK) { + return ret; + } + } + to_remove = knot_rdataset_next(to_remove); + } + + return KNOT_EOK; +} diff --git a/src/libknot/rdataset.h b/src/libknot/rdataset.h new file mode 100644 index 0000000..5c49847 --- /dev/null +++ b/src/libknot/rdataset.h @@ -0,0 +1,195 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \brief API for manipulating RR arrays. + * + * \addtogroup rr + * @{ + */ + +#pragma once + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "libknot/errcode.h" +#include "libknot/mm_ctx.h" +#include "libknot/rdata.h" + +/*!< \brief Set of RRs. */ +typedef struct { + uint16_t count; /*!< \brief Count of RRs stored in the structure. */ + uint32_t size; /*!< \brief Size of the rdata array. */ + knot_rdata_t *rdata; /*!< \brief Serialized rdata, canonically sorted. */ +} knot_rdataset_t; + +/*! + * \brief Initializes RRS structure. + * + * \param rrs Structure to be initialized. + */ +inline static void knot_rdataset_init(knot_rdataset_t *rrs) +{ + if (rrs != NULL) { + rrs->count = 0; + rrs->size = 0; + rrs->rdata = NULL; + } +} + +/*! + * \brief Advance to the next rdata in a rdataset. + * + * Useful for iteration. + * + * \note Ensure that this operation makes sense! + * + * \param rr Current RR. + * + * \return Next RR. + */ +static inline knot_rdata_t *knot_rdataset_next(knot_rdata_t *rr) +{ + assert(rr); + return (knot_rdata_t *)((uint8_t *)rr + knot_rdata_size(rr->len)); +} + +/*! + * \brief Frees data initialized by RRS structure, but not the structure itself. + * + * \param rrs Structure to be cleared. + * \param mm Memory context used to create allocations. + */ +void knot_rdataset_clear(knot_rdataset_t *rrs, knot_mm_t *mm); + +/*! + * \brief Deep copies RRS structure. All data are duplicated. + * + * \param dst Copy destination. + * \param src Copy source. + * \param mm Memory context. + * + * \return KNOT_E* + */ +int knot_rdataset_copy(knot_rdataset_t *dst, const knot_rdataset_t *src, knot_mm_t *mm); + +/*! + * \brief Gets RR from RRS structure, using given position. + * + * \param rrs RRS structure to get RR from. + * \param pos Position to use (counted from 0). + * + * \return Pointer to RR at \a pos position. + */ +knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *rrs, uint16_t pos); + +/*! + * \brief Adds single RR into RRS structure. All data are copied. + * + * \param rrs RRS structure to add RR into. + * \param rr RR to add. + * \param mm Memory context. + * + * \return KNOT_E* + */ +int knot_rdataset_add(knot_rdataset_t *rrs, const knot_rdata_t *rr, knot_mm_t *mm); + +/*! + * \brief RRS equality check. + * + * \param rrs1 First RRS to be compared. + * \param rrs2 Second RRS to be compared. + * + * \retval true if rrs1 == rrs2. + * \retval false if rrs1 != rrs2. + */ +bool knot_rdataset_eq(const knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2); + +/*! + * \brief Returns true if \a rr is present in \a rrs, false otherwise. + * + * \param rrs RRS to search in. + * \param rr RR to compare with. + * + * \retval true if \a rr is present in \a rrs. + * \retval false if \a rr is not present in \a rrs. + */ +bool knot_rdataset_member(const knot_rdataset_t *rrs, const knot_rdata_t *rr); + +/*! + * \brief Merges two RRS into the first one. Second RRS is left intact. + * Canonical order is preserved. + * + * \param rrs1 Destination RRS (merge here). + * \param rrs2 RRS to be merged (merge from). + * \param mm Memory context. + * + * \return KNOT_E* + */ +int knot_rdataset_merge(knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2, + knot_mm_t *mm); + +/*! + * \brief RRS set-like intersection. Full compare is done. + * + * \param rrs1 First RRS to intersect. + * \param rrs2 Second RRS to intersect. + * \param out Output RRS with intersection, RDATA are created anew. + * \param mm Memory context. Will be used to create new RDATA. + * + * \return KNOT_E* + */ +int knot_rdataset_intersect(const knot_rdataset_t *rrs1, const knot_rdataset_t *rrs2, + knot_rdataset_t *out, knot_mm_t *mm); + +/*! + * \brief Does set-like RRS subtraction. \a from RRS is changed. + * + * \param from RRS to subtract from. + * \param what RRS to subtract. + * \param mm Memory context use to reallocated \a from data. + * + * \return KNOT_E* + */ +int knot_rdataset_subtract(knot_rdataset_t *from, const knot_rdataset_t *what, + knot_mm_t *mm); + +/*! + * \brief Removes single RR from RRS structure. + * + * \param rrs RRS structure to remove RR from. + * \param rr RR to remove. + * \param mm Memory context. + * + * \return KNOT_E* + */ +inline static int knot_rdataset_remove(knot_rdataset_t *rrs, const knot_rdata_t *rr, + knot_mm_t *mm) +{ + if (rr == NULL) { + return KNOT_EINVAL; + } + + knot_rdataset_t rrs_rm = { 1, knot_rdata_size(rr->len), (knot_rdata_t *)rr }; + return knot_rdataset_subtract(rrs, &rrs_rm, mm); +} + +/*! @} */ diff --git a/src/libknot/rrset-dump.c b/src/libknot/rrset-dump.c new file mode 100644 index 0000000..478539a --- /dev/null +++ b/src/libknot/rrset-dump.c @@ -0,0 +1,2108 @@ +/* Copyright (C) 2020 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 <arpa/inet.h> +#include <inttypes.h> +#include <math.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "libdnssec/binary.h" +#include "libdnssec/keytag.h" +#include "libknot/attribute.h" +#include "libknot/rrset-dump.h" +#include "libknot/codes.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/errcode.h" +#include "libknot/lookup.h" +#include "libknot/rrtype/rrsig.h" +#include "libknot/wire.h" +#include "contrib/base32hex.h" +#include "contrib/base64.h" +#include "contrib/ctype.h" +#include "contrib/wire_ctx.h" + +#define RRSET_DUMP_LIMIT (2 * 1024 * 1024) + +#define TAB_WIDTH 8 +#define BLOCK_WIDTH 40 +#define BLOCK_INDENT "\n\t\t\t\t" + +#define LOC_ZERO 2147483648 // 2^31 + +/*! \brief macros with repetitive (mostly error-checking) code of methods from first section of this file */ +#define CHECK_PRET if (p->ret < 0) return; +#define CHECK_INMAX(mininmax) if (p->in_max < (mininmax)) { p->ret = -1; return; } +#define CHECK_RET_OUTMAX_SNPRINTF if (ret <= 0 || (size_t)ret >= p->out_max) { p->ret = -1; return; } +#define STRING_TERMINATION if (p->out_max > 0) { *p->out = '\0'; } else { p->ret = -1; return; } +#define FILL_IN_INPUT(pdata) if (memcpy(&(pdata), p->in, in_len) == NULL) { p->ret = -1; return; } +#define CHECK_RET_POSITIVE if (ret <= 0) { p->ret = -1; return; } + +#define SNPRINTF_CHECK(ret, max_len) \ + if ((ret) < 0 || (size_t)(ret) >= (max_len)) { \ + return KNOT_ESPACE; \ + } + +typedef struct { + const knot_dump_style_t *style; + const uint8_t *in; + size_t in_max; + char *out; + size_t out_max; + size_t total; + int ret; +} rrset_dump_params_t; + +_public_ +const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT = { + .wrap = false, + .show_class = false, + .show_ttl = true, + .verbose = false, + .original_ttl = true, + .empty_ttl = false, + .human_ttl = false, + .human_tmstamp = true, + .hide_crypto = false, + .ascii_to_idn = NULL +}; + +static void dump_string(rrset_dump_params_t *p, const char *str) +{ + CHECK_PRET + + size_t in_len = strlen(str); + + // Check input size (+ 1 termination). + if (in_len >= p->out_max) { + p->ret = -1; + return; + } + + // Copy string including termination '\0'! + if (memcpy(p->out, str, in_len + 1) == NULL) { + p->ret = -1; + return; + } + + // Fill in output. + p->out += in_len; + p->out_max -= in_len; + p->total += in_len; +} + +static void wire_num8_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint8_t data = *(p->in); + size_t in_len = sizeof(data); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + // Write number. + int ret = snprintf(p->out, p->out_max, "%u", data); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_num16_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint16_t data; + size_t in_len = sizeof(data); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + // Fill in input data. + data = knot_wire_read_u16(p->in); + + // Write number. + int ret = snprintf(p->out, p->out_max, "%u", data); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_num32_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint32_t data; + size_t in_len = sizeof(data); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + // Fill in input data. + data = knot_wire_read_u32(p->in); + + // Write number. + int ret = snprintf(p->out, p->out_max, "%u", data); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_num48_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint64_t data; + size_t in_len = 6; + size_t out_len = 0; + + CHECK_INMAX(in_len) + + // Fill in input data. + data = knot_wire_read_u48(p->in); + + // Write number. + int ret = snprintf(p->out, p->out_max, "%"PRIu64"", data); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_ipv4_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + struct in_addr addr4; + size_t in_len = sizeof(addr4.s_addr); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + FILL_IN_INPUT(addr4.s_addr) + + // Write address. + if (inet_ntop(AF_INET, &addr4, p->out, p->out_max) == NULL) { + p->ret = -1; + return; + } + out_len = strlen(p->out); + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_ipv6_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + struct in6_addr addr6; + size_t in_len = sizeof(addr6.s6_addr); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + FILL_IN_INPUT(addr6.s6_addr) + + // Write address. + if (inet_ntop(AF_INET6, &addr6, p->out, p->out_max) == NULL) { + p->ret = -1; + return; + } + out_len = strlen(p->out); + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_type_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + char type[32]; + uint16_t data; + size_t in_len = sizeof(data); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + FILL_IN_INPUT(data) + + // Get record type name string. + int ret = knot_rrtype_to_string(ntohs(data), type, sizeof(type)); + CHECK_RET_POSITIVE + + // Write string. + ret = snprintf(p->out, p->out_max, "%s", type); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static int hex_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + static const char hex[] = "0123456789ABCDEF"; + + if (out_len < 2 * in_len) { + return -1; + } + + for (uint32_t i = 0; i < in_len; i++) { + out[2 * i] = hex[in[i] / 16]; + out[2 * i + 1] = hex[in[i] % 16]; + } + + return 2 * in_len; +} + +static int hex_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + uint32_t out_len = 2 * in_len; + + // Allocating output buffer. + *out = malloc(out_len); + + if (*out == NULL) { + return -1; + } + + // Encoding data. + return hex_encode(in, in_len, *out, out_len); +} + +static int num48_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + if (in_len != 6) { + return -1; + } + + uint64_t data = knot_wire_read_u48(in); + + int ret = snprintf((char *)out, out_len, "%"PRIu64"", data); + if (ret <= 0 || (size_t)ret >= out_len) { + return -1; + } + + return ret; +} + +typedef int (*encode_t)(const uint8_t *in, const uint32_t in_len, + uint8_t *out, const uint32_t out_len); + +typedef int (*encode_alloc_t)(const uint8_t *in, const uint32_t in_len, + uint8_t **out); + +static void wire_data_encode_to_str(rrset_dump_params_t *p, + encode_t enc, encode_alloc_t enc_alloc) +{ + CHECK_PRET + + int ret; + size_t in_len = p->in_max; + + // One-line vs multi-line mode. + if (p->style->wrap == false) { + // Encode data directly to the output. + ret = enc(p->in, in_len, (uint8_t *)(p->out), p->out_max); + CHECK_RET_POSITIVE + size_t out_len = ret; + + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; + } else { + int src_begin; + uint8_t *buf; + + // Encode data to the temporary buffer. + ret = enc_alloc(p->in, in_len, &buf); + CHECK_RET_POSITIVE + + // Loop which wraps base64 block in more lines. + for (src_begin = 0; src_begin < ret; src_begin += BLOCK_WIDTH) { + if (src_begin > 0) { + // Write indent block. + dump_string(p, BLOCK_INDENT); + if (p->ret < 0) { + free(buf); + return; + } + } + + // Compute block length (the last one can be shorter). + int src_len = (ret - src_begin) < BLOCK_WIDTH ? + (ret - src_begin) : BLOCK_WIDTH; + + if ((size_t)src_len > p->out_max) { + free(buf); + p->ret = -1; + return; + } + + // Write data block. + memcpy(p->out, buf + src_begin, src_len); + + p->out += src_len; + p->out_max -= src_len; + p->total += src_len; + } + + // Destroy temporary buffer. + free(buf); + } + + STRING_TERMINATION + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; +} + +static void wire_len_data_encode_to_str(rrset_dump_params_t *p, + encode_t enc, + const size_t len_len, + const bool print_len, + const char *empty_str) +{ + CHECK_PRET + + size_t in_len; + + // First len_len bytes are data length. + CHECK_INMAX(len_len) + + // Read data length. + switch (len_len) { + case 1: + in_len = *(p->in); + break; + case 2: + in_len = knot_wire_read_u16(p->in); + break; + case 4: + in_len = knot_wire_read_u32(p->in); + break; + default: + p->ret = -1; + return; + } + + // If required print data length. + if (print_len == true) { + switch (len_len) { + case 1: + wire_num8_to_str(p); + break; + case 2: + wire_num16_to_str(p); + break; + case 4: + wire_num32_to_str(p); + break; + } + + CHECK_PRET + + // If something follows, print one space character. + if (in_len > 0 || *empty_str != '\0') { + dump_string(p, " "); + CHECK_PRET + } + } else { + p->in += len_len; + p->in_max -= len_len; + } + + if (in_len > 0) { + // Encode data directly to the output. + int ret = enc(p->in, in_len, (uint8_t *)(p->out), p->out_max); + CHECK_RET_POSITIVE + p->out += ret; + p->out_max -= ret; + p->total += ret; + + STRING_TERMINATION + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + } else if (*empty_str != '\0') { + dump_string(p, empty_str); + CHECK_PRET + } +} + +static void wire_data_omit(rrset_dump_params_t *p) +{ + CHECK_PRET + + const char *omit_message = "[omitted]"; + const size_t omlen = strlen(omit_message); + + if (p->out_max < omlen) { + p->ret = -1; + return; + } + + memcpy(p->out, omit_message, omlen); + p->out += omlen; + p->out_max -= omlen; + p->total += omlen; + + STRING_TERMINATION + + p->in += p->in_max; + p->in_max = 0; +} + +static void wire_dnskey_to_tag(rrset_dump_params_t *p) +{ + CHECK_PRET + + int key_pos = -4; // we expect that key flags, 3 and algorithm + // have been already dumped + + uint16_t key_tag = 0; + const dnssec_binary_t rdata_bin = { + .data = (uint8_t *)(p->in + key_pos), + .size = p->in_max - key_pos + }; + dnssec_keytag(&rdata_bin, &key_tag); + + int ret = snprintf(p->out, p->out_max, "[id = %hu]", key_tag); + CHECK_RET_OUTMAX_SNPRINTF + + p->in += p->in_max; + p->in_max = 0; + p->out += ret; + p->out_max -= ret; + p->total += ret; +} + +static void wire_unknown_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + int ret; + size_t in_len = p->in_max; + size_t out_len = 0; + + // Write unknown length header. + if (in_len > 0) { + ret = snprintf(p->out, p->out_max, "\\# %zu ", in_len); + } else { + ret = snprintf(p->out, p->out_max, "\\# 0"); + } + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + + // Fill in output. + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; + + // Write hex data if any. + if (in_len > 0) { + // If wrap mode wrap line. + if (p->style->wrap) { + dump_string(p, BLOCK_INDENT); + CHECK_PRET + } + + wire_data_encode_to_str(p, &hex_encode, &hex_encode_alloc); + CHECK_PRET + } +} + +static void wire_text_to_str(rrset_dump_params_t *p, bool quote, bool with_header) +{ + CHECK_PRET + + size_t in_len = 0; + + if (with_header) { + // First byte is string length. + CHECK_INMAX(1) + in_len = *(p->in); + p->in++; + p->in_max--; + + // Check if the given length makes sense. + CHECK_INMAX(in_len) + } else { + in_len = p->in_max; + } + + // Check if quotation can ever be disabled (parser protection fallback). + if (!quote) { + for (size_t i = 0; i < in_len; i++) { + if (p->in[i] == ' ') { // Other WS characters are encoded. + quote = true; + break; + } + } + } + + // Opening quotation. + if (quote) { + dump_string(p, "\""); + CHECK_PRET + } + + // Loop over all characters. + for (size_t i = 0; i < in_len; i++) { + uint8_t ch = p->in[i]; + + if (is_print(ch)) { + // For special character print leading slash. + if (ch == '\\' || ch == '"') { + dump_string(p, "\\"); + CHECK_PRET + } + + // Print text character. + if (p->out_max == 0) { + p->ret = -1; + return; + } + + *p->out = ch; + p->out++; + p->out_max--; + p->total++; + } else { + // Unprintable character encode via \ddd notation. + int ret = snprintf(p->out, p->out_max,"\\%03u", ch); + CHECK_RET_OUTMAX_SNPRINTF + + p->out += ret; + p->out_max -= ret; + p->total += ret; + } + } + + // Closing quotation. + if (quote) { + dump_string(p, "\""); + CHECK_PRET + } + + STRING_TERMINATION + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; +} + +static void wire_timestamp_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint32_t data; + size_t in_len = sizeof(data); + size_t out_len = 0; + int ret; + + CHECK_INMAX(in_len) + + FILL_IN_INPUT(data) + + time_t timestamp = ntohl(data); + + if (p->style->human_tmstamp) { + struct tm result; + // Write timestamp in YYYYMMDDhhmmss format. + ret = strftime(p->out, p->out_max, "%Y%m%d%H%M%S", + gmtime_r(×tamp, &result)); + CHECK_RET_POSITIVE + } else { + // Write timestamp only. + ret = snprintf(p->out, p->out_max, "%u", ntohl(data)); + CHECK_RET_OUTMAX_SNPRINTF + } + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static int time_to_human_str(char *out, + const size_t out_len, + uint32_t data) +{ + size_t total_len = 0; + uint32_t num; + int ret; + +#define tths_process(unit_name, unit_size) \ + num = data / (unit_size); \ + if (num > 0) { \ + ret = snprintf(out + total_len, out_len - total_len, \ + "%u%s", num, (unit_name)); \ + if (ret <= 0 || (size_t)ret >= out_len - total_len) { \ + return -1; \ + } \ + total_len += ret; \ + data -= num * (unit_size); \ + } + + tths_process("d", 86400); + tths_process("h", 3600); + tths_process("m", 60); + tths_process("s", 1); + +#undef tths_process + + return total_len > 0 ? total_len : -1; +} + +static void wire_ttl_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint32_t data; + size_t in_len = sizeof(data); + size_t out_len = 0; + int ret; + + CHECK_INMAX(in_len) + + FILL_IN_INPUT(data) + + if (p->style->human_ttl) { + // Write time in human readable format. + ret = time_to_human_str(p->out, p->out_max, ntohl(data)); + CHECK_RET_POSITIVE + } else { + // Write timestamp only. + ret = snprintf(p->out, p->out_max, "%u", ntohl(data)); + CHECK_RET_OUTMAX_SNPRINTF + } + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_bitmap_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + int ret; + char type[32]; + size_t i = 0; + size_t in_len = p->in_max; + size_t out_len = 0; + + // Loop over bitmap window array (can be empty). + while (i < in_len) { + // First byte is window number. + uint8_t win = p->in[i++]; + + // Check window length (length must follow). + if (i >= in_len) { + p->ret = -1; + return; + } + + // Second byte is window length. + uint8_t bitmap_len = p->in[i++]; + + // Check window length (len bytes must follow). + if (i + bitmap_len > in_len) { + p->ret = -1; + return; + } + + // Bitmap processing. + for (size_t j = 0; j < (bitmap_len * 8); j++) { + if ((p->in[i + j / 8] & (128 >> (j % 8))) != 0) { + uint16_t type_num = win * 256 + j; + + ret = knot_rrtype_to_string(type_num, type, sizeof(type)); + CHECK_RET_POSITIVE + + // Print type name to type list. + if (out_len > 0) { + ret = snprintf(p->out, p->out_max, + " %s", type); + } else { + ret = snprintf(p->out, p->out_max, + "%s", type); + } + CHECK_RET_OUTMAX_SNPRINTF + out_len += ret; + p->out += ret; + p->out_max -= ret; + } + } + + i += bitmap_len; + } + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->total += out_len; +} + +static void wire_dname_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + size_t in_len = knot_dname_size(p->in); + size_t out_len = 0; + + CHECK_INMAX(in_len) + + // Write dname string. + if (p->style->ascii_to_idn == NULL) { + char *dname_str = knot_dname_to_str(p->out, p->in, p->out_max); + if (dname_str == NULL) { + p->ret = -1; + return; + } + out_len = strlen(dname_str); + } else { + char *dname_str = knot_dname_to_str_alloc(p->in); + p->style->ascii_to_idn(&dname_str); + + int ret = snprintf(p->out, p->out_max, "%s", dname_str); + free(dname_str); + CHECK_RET_OUTMAX_SNPRINTF + out_len = ret; + } + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static void wire_apl_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + struct in_addr addr4; + struct in6_addr addr6; + int ret; + size_t out_len = 0; + + // Input check: family(2B) + prefix(1B) + afdlen(1B). + CHECK_INMAX(4) + + // Read fixed size values. + uint16_t family = knot_wire_read_u16(p->in); + uint8_t prefix = *(p->in + 2); + uint8_t negation = *(p->in + 3) >> 7; + uint8_t afdlen = *(p->in + 3) & 0x7F; + p->in += 4; + p->in_max -= 4; + + // Write negation mark. + if (negation != 0) { + dump_string(p, "!"); + CHECK_PRET + } + + // Write address family with colon. + ret = snprintf(p->out, p->out_max, "%u:", family); + CHECK_RET_OUTMAX_SNPRINTF + p->out += ret; + p->out_max -= ret; + p->total += ret; + + // Write address. + switch (family) { + case 1: + memset(&addr4, 0, sizeof(addr4)); + + if (afdlen > sizeof(addr4.s_addr) || afdlen > p->in_max) { + p->ret = -1; + return; + } + + if (memcpy(&(addr4.s_addr), p->in, afdlen) == NULL) { + p->ret = -1; + return; + } + + // Write address. + if (inet_ntop(AF_INET, &addr4, p->out, p->out_max) == NULL) { + p->ret = -1; + return; + } + out_len = strlen(p->out); + + break; + case 2: + memset(&addr6, 0, sizeof(addr6)); + + if (afdlen > sizeof(addr6.s6_addr) || afdlen > p->in_max) { + p->ret = -1; + return; + } + + if (memcpy(&(addr6.s6_addr), p->in, afdlen) == NULL) { + p->ret = -1; + return; + } + + // Write address. + if (inet_ntop(AF_INET6, &addr6, p->out, p->out_max) == NULL) { + p->ret = -1; + return; + } + out_len = strlen(p->out); + + break; + default: + p->ret = -1; + return; + } + p->in += afdlen; + p->in_max -= afdlen; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; + + // Write prefix length with forward slash. + ret = snprintf(p->out, p->out_max, "/%u", prefix); + CHECK_RET_OUTMAX_SNPRINTF + p->out += ret; + p->out_max -= ret; + p->total += ret; +} + +static void wire_loc_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + // Read values. + wire_ctx_t wire = wire_ctx_init_const(p->in, p->in_max); + uint8_t version = wire_ctx_read_u8(&wire); + + // Version check. + if (version != 0) { + wire_unknown_to_str(p); + p->ret = -1; + return; + } + + // Continue to read values. + uint8_t size_w = wire_ctx_read_u8(&wire); + uint8_t hpre_w = wire_ctx_read_u8(&wire); + uint8_t vpre_w = wire_ctx_read_u8(&wire); + uint32_t lat_w = wire_ctx_read_u32(&wire); + uint32_t lon_w = wire_ctx_read_u32(&wire); + uint32_t alt_w = wire_ctx_read_u32(&wire); + + // Check if all reads are correct. + if (wire.error != KNOT_EOK) { + p->ret = -1; + return; + } + + p->in += wire_ctx_offset(&wire); + p->in_max = wire_ctx_available(&wire); + + // Latitude calculation. + char lat_mark; + uint32_t lat; + if (lat_w >= LOC_ZERO) { + lat_mark = 'N'; + lat = lat_w - LOC_ZERO; + } else { + lat_mark = 'S'; + lat = LOC_ZERO - lat_w; + } + + uint32_t d1 = lat / 3600000; + uint32_t m1 = (lat - 3600000 * d1) / 60000; + double s1 = 0.001 * (lat - 3600000 * d1 - 60000 * m1); + + // Longitude calculation. + char lon_mark; + uint32_t lon; + if (lon_w >= LOC_ZERO) { + lon_mark = 'E'; + lon = lon_w - LOC_ZERO; + } else { + lon_mark = 'W'; + lon = LOC_ZERO - lon_w; + } + + uint32_t d2 = lon / 3600000; + uint32_t m2 = (lon - 3600000 * d2) / 60000; + double s2 = 0.001 * (lon - 3600000 * d2 - 60000 * m2); + + // Write latitude and longitude. + int ret = snprintf(p->out, p->out_max, "%u %u %.*f %c %u %u %.*f %c", + d1, m1, (uint32_t)s1 != s1 ? 3 : 0, s1, lat_mark, + d2, m2, (uint32_t)s2 != s2 ? 3 : 0, s2, lon_mark); + CHECK_RET_OUTMAX_SNPRINTF + p->out += ret; + p->out_max -= ret; + p->total += ret; + + // Altitude calculation. + double alt = 0.01 * alt_w - 100000.0; + + // Compute mantisa and exponent for each size. + uint8_t size_m = size_w >> 4; + uint8_t size_e = size_w & 0xF; + uint8_t hpre_m = hpre_w >> 4; + uint8_t hpre_e = hpre_w & 0xF; + uint8_t vpre_m = vpre_w >> 4; + uint8_t vpre_e = vpre_w & 0xF; + + // Sizes check. + if (size_m > 9 || size_e > 9 || hpre_m > 9 || hpre_e > 9 || + vpre_m > 9 || vpre_e > 9) { + p->ret = -1; + return; + } + + // Size and precisions calculation. + double size = 0.01 * size_m * pow(10, size_e); + double hpre = 0.01 * hpre_m * pow(10, hpre_e); + double vpre = 0.01 * vpre_m * pow(10, vpre_e); + + // Write altitude and precisions. + ret = snprintf(p->out, p->out_max, " %.*fm %.*fm %.*fm %.*fm", + (int32_t)alt != alt ? 2 : 0, alt, + (uint32_t)size != size ? 2 : 0, size, + (uint32_t)hpre != hpre ? 2 : 0, hpre, + (uint32_t)vpre != vpre ? 2 : 0, vpre); + CHECK_RET_OUTMAX_SNPRINTF + p->out += ret; + p->out_max -= ret; + p->total += ret; +} + +static void wire_gateway_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + // Input check: type(1B) + algo(1B). + CHECK_INMAX(2) + + uint8_t type = *p->in; + uint8_t alg = *(p->in + 1); + + // Write gateway type. + wire_num8_to_str(p); + CHECK_PRET + + // Write space. + dump_string(p, " "); + CHECK_PRET + + // Write algorithm number. + wire_num8_to_str(p); + CHECK_PRET + + // Write space. + dump_string(p, " "); + CHECK_PRET + + // Write appropriate gateway. + switch (type) { + case 0: + dump_string(p, "."); + break; + case 1: + wire_ipv4_to_str(p); + break; + case 2: + wire_ipv6_to_str(p); + break; + case 3: + wire_dname_to_str(p); + break; + default: + p->ret = -1; + } + CHECK_PRET + + if (alg > 0) { + // If wrap mode wrap line. + if (p->style->wrap) { + dump_string(p, BLOCK_INDENT); + } else { + dump_string(p, " "); + } + CHECK_PRET + + // Write ipsec key. + wire_data_encode_to_str(p, &knot_base64_encode, &knot_base64_encode_alloc); + CHECK_PRET + } +} + +static void wire_l64_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + // Check input size (64-bit identifier). + if (p->in_max != 8) { + p->ret = -1; + return; + } + + // Write identifier (2-byte) labels separated with a colon. + while (p->in_max > 0) { + int ret = hex_encode(p->in, 2, (uint8_t *)(p->out), p->out_max); + CHECK_RET_POSITIVE + p->in += 2; + p->in_max -= 2; + p->out += ret; + p->out_max -= ret; + p->total += ret; + + // Write separation character. + if (p->in_max > 0) { + dump_string(p, ":"); + CHECK_PRET + } + } +} + +static void wire_eui_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + CHECK_INMAX(2) + + // Write EUI hexadecimal pairs. + while (p->in_max > 0) { + int ret = hex_encode(p->in, 1, (uint8_t *)(p->out), p->out_max); + CHECK_RET_POSITIVE + p->in++; + p->in_max--; + p->out += ret; + p->out_max -= ret; + p->total += ret; + + // Write separation character. + if (p->in_max > 0) { + dump_string(p, "-"); + CHECK_PRET + } + } +} + +static void wire_tsig_rcode_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + uint16_t data; + size_t in_len = sizeof(data); + const char *rcode_str = "Unknown"; + + CHECK_INMAX(in_len) + + // Fill in input data. + data = knot_wire_read_u16(p->in); + + // Find RCODE name. + const knot_lookup_t *rcode = NULL; + rcode = knot_lookup_by_id(knot_tsig_rcode_names, data); + if (rcode == NULL) { + rcode = knot_lookup_by_id(knot_rcode_names, data); + } + if (rcode != NULL) { + rcode_str = rcode->name; + } + + // Dump RCODE name. + dump_string(p, rcode_str); + CHECK_PRET + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; +} + +static size_t dnskey_len(const uint8_t *rdata, + const size_t rdata_len) +{ + // Check for empty rdata and empty key. + if (rdata_len <= 4) { + return 0; + } + + const uint8_t *key = rdata + 4; + const size_t len = rdata_len - 4; + + switch (rdata[3]) { + case KNOT_DNSSEC_ALG_DSA: + case KNOT_DNSSEC_ALG_DSA_NSEC3_SHA1: + // RFC 2536, key size ~ bit-length of 'modulus' P. + return (64 + 8 * key[0]) * 8; + case KNOT_DNSSEC_ALG_RSAMD5: + case KNOT_DNSSEC_ALG_RSASHA1: + case KNOT_DNSSEC_ALG_RSASHA1_NSEC3_SHA1: + case KNOT_DNSSEC_ALG_RSASHA256: + case KNOT_DNSSEC_ALG_RSASHA512: + // RFC 3110, key size ~ bit-length of 'modulus'. + if (key[0] == 0) { + if (len < 3) { + return 0; + } + uint16_t exp; + memcpy(&exp, key + 1, sizeof(uint16_t)); + return (len - 3 - ntohs(exp)) * 8; + } else { + return (len - 1 - key[0]) * 8; + } + case KNOT_DNSSEC_ALG_ECC_GOST: + // RFC 5933, key size of GOST public keys MUST be 512 bits. + return 512; + case KNOT_DNSSEC_ALG_ECDSAP256SHA256: + // RFC 6605. + return 256; + case KNOT_DNSSEC_ALG_ECDSAP384SHA384: + // RFC 6605. + return 384; + case KNOT_DNSSEC_ALG_ED25519: + // RFC 8080. + return 256; + case KNOT_DNSSEC_ALG_ED448: + // RFC 8080. + return 456; + default: + return 0; + } +} + +static int ber_to_oid(char *dst, + size_t dst_len, + const uint8_t *src, + const size_t src_len) +{ + assert(dst); + assert(src); + + static const uint8_t longer_mask = (1 << 7); + + size_t len = src[0]; + if (len == 0 || len >= src_len || dst_len == 0) { + return KNOT_EINVAL; + } + + uint64_t node = 0UL; + for (int i = 1; i <= len; ++i) { + uint8_t longer_node = (src[i] & longer_mask); + node <<= 7; + node += (longer_node ^ src[i]); + if (!longer_node) { + int ret = snprintf(dst, dst_len, "%"PRIu64".", node); + SNPRINTF_CHECK(ret, dst_len); + dst += ret; + dst_len -= ret; + node = 0UL; + } + } + *(dst - 1) = '\0'; + + return KNOT_EOK; +} + +static void dnskey_info(const uint8_t *rdata, + const size_t rdata_len, + char *out, + const size_t out_len) +{ + if (rdata_len < 5) { + return; + } + + const uint8_t sep = *(rdata + 1) & 0x01; + uint16_t key_tag = 0; + const size_t key_len = dnskey_len(rdata, rdata_len); + const uint8_t alg_id = rdata[3]; + char alg_info[512] = ""; + + const dnssec_binary_t rdata_bin = { .data = (uint8_t *)rdata, + .size = rdata_len }; + dnssec_keytag(&rdata_bin, &key_tag); + + const knot_lookup_t *alg = knot_lookup_by_id(knot_dnssec_alg_names, alg_id); + + switch (alg_id) { + case KNOT_DNSSEC_ALG_DELETE: + case KNOT_DNSSEC_ALG_INDIRECT: + break; + case KNOT_DNSSEC_ALG_PRIVATEOID: + ; char oid_str[sizeof(alg_info) - 3]; + if (ber_to_oid(oid_str, sizeof(oid_str), rdata + 4, rdata_len - 4) != KNOT_EOK || + snprintf(alg_info, sizeof(alg_info), " (%s)", oid_str) <= 0) { + alg_info[0] = '\0'; + } + break; + case KNOT_DNSSEC_ALG_PRIVATEDNS: + ; knot_dname_txt_storage_t alg_str; + if (knot_dname_wire_check(rdata + 4, rdata + rdata_len, NULL) <= 0 || + knot_dname_to_str(alg_str, rdata + 4, sizeof(alg_str)) == NULL || + snprintf(alg_info, sizeof(alg_info), " (%s)", alg_str) <= 0) { + alg_info[0] = '\0'; + } + break; + default: + if (snprintf(alg_info, sizeof(alg_info), " (%zub)", key_len) <= 0) { + alg_info[0] = '\0'; + } + break; + } + + int ret = snprintf(out, out_len, "%s, %s%s, id = %u", + sep ? "KSK" : "ZSK", + alg ? alg->name : "UNKNOWN", + alg_info, + key_tag); + if (ret <= 0) { // Truncated return is acceptable. Just check for errors. + out[0] = '\0'; + } +} + +#define DUMP_PARAMS rrset_dump_params_t *const p +#define DUMP_END return (p->in_max == 0 ? (int)p->total : KNOT_EPARSEFAIL); + +#define CHECK_RET(p) if (p->ret < 0) return p->ret; + +#define WRAP_INIT dump_string(p, "(" BLOCK_INDENT); CHECK_RET(p); +#define WRAP_END dump_string(p, BLOCK_INDENT ")"); CHECK_RET(p); +#define WRAP_LINE dump_string(p, BLOCK_INDENT); CHECK_RET(p); + +#define COMMENT(s) if (p->style->verbose) { \ + dump_string(p, " ; "); CHECK_RET(p); \ + dump_string(p, s); CHECK_RET(p); \ + } + +#define DUMP_SPACE dump_string(p, " "); CHECK_RET(p); +#define DUMP_NUM8 wire_num8_to_str(p); CHECK_RET(p); +#define DUMP_NUM16 wire_num16_to_str(p); CHECK_RET(p); +#define DUMP_NUM32 wire_num32_to_str(p); CHECK_RET(p); +#define DUMP_NUM48 wire_num48_to_str(p); CHECK_RET(p); +#define DUMP_DNAME wire_dname_to_str(p); CHECK_RET(p); +#define DUMP_TIME wire_ttl_to_str(p); CHECK_RET(p); +#define DUMP_TIMESTAMP wire_timestamp_to_str(p); CHECK_RET(p); +#define DUMP_IPV4 wire_ipv4_to_str(p); CHECK_RET(p); +#define DUMP_IPV6 wire_ipv6_to_str(p); CHECK_RET(p); +#define DUMP_TYPE wire_type_to_str(p); CHECK_RET(p); +#define DUMP_HEX wire_data_encode_to_str(p, &hex_encode, \ + &hex_encode_alloc); CHECK_RET(p); +#define DUMP_BASE64 wire_data_encode_to_str(p, &knot_base64_encode, \ + &knot_base64_encode_alloc); CHECK_RET(p); +#define DUMP_HASH wire_len_data_encode_to_str(p, &knot_base32hex_encode, \ + 1, false, ""); CHECK_RET(p); +#define DUMP_SALT wire_len_data_encode_to_str(p, &hex_encode, \ + 1, false, "-"); CHECK_RET(p); +#define DUMP_TSIG_DGST wire_len_data_encode_to_str(p, &knot_base64_encode, \ + 2, true, ""); CHECK_RET(p); +#define DUMP_TSIG_DATA wire_len_data_encode_to_str(p, &num48_encode, \ + 2, true, ""); CHECK_RET(p); +#define DUMP_OMIT wire_data_omit(p); CHECK_RET(p); +#define DUMP_KEY_OMIT wire_dnskey_to_tag(p); CHECK_RET(p); +#define DUMP_TEXT wire_text_to_str(p, true, true); CHECK_RET(p); +#define DUMP_LONG_TEXT wire_text_to_str(p, true, false); CHECK_RET(p); +#define DUMP_UNQUOTED wire_text_to_str(p, false, true); CHECK_RET(p); +#define DUMP_BITMAP wire_bitmap_to_str(p); CHECK_RET(p); +#define DUMP_APL wire_apl_to_str(p); CHECK_RET(p); +#define DUMP_LOC wire_loc_to_str(p); CHECK_RET(p); +#define DUMP_GATEWAY wire_gateway_to_str(p); CHECK_RET(p); +#define DUMP_L64 wire_l64_to_str(p); CHECK_RET(p); +#define DUMP_EUI wire_eui_to_str(p); CHECK_RET(p); +#define DUMP_TSIG_RCODE wire_tsig_rcode_to_str(p); CHECK_RET(p); +#define DUMP_UNKNOWN wire_unknown_to_str(p); CHECK_RET(p); + +static int dump_a(DUMP_PARAMS) +{ + DUMP_IPV4; + + DUMP_END; +} + +static int dump_ns(DUMP_PARAMS) +{ + DUMP_DNAME; + + DUMP_END; +} + +static int dump_soa(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_DNAME; DUMP_SPACE; + DUMP_DNAME; DUMP_SPACE; WRAP_INIT; + DUMP_NUM32; COMMENT("serial"); WRAP_LINE; + DUMP_TIME; COMMENT("refresh"); WRAP_LINE; + DUMP_TIME; COMMENT("retry"); WRAP_LINE; + DUMP_TIME; COMMENT("expire"); WRAP_LINE; + DUMP_TIME; COMMENT("minimum"); WRAP_END; + } else { + DUMP_DNAME; DUMP_SPACE; + DUMP_DNAME; DUMP_SPACE; + DUMP_NUM32; DUMP_SPACE; + DUMP_TIME; DUMP_SPACE; + DUMP_TIME; DUMP_SPACE; + DUMP_TIME; DUMP_SPACE; + DUMP_TIME; + } + + DUMP_END; +} + +static int dump_hinfo(DUMP_PARAMS) +{ + DUMP_TEXT; DUMP_SPACE; + DUMP_TEXT; + + DUMP_END; +} + +static int dump_minfo(DUMP_PARAMS) +{ + DUMP_DNAME; DUMP_SPACE; + DUMP_DNAME; + + DUMP_END; +} + +static int dump_mx(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_DNAME; + + DUMP_END; +} + +static int dump_txt(DUMP_PARAMS) +{ + // First text string. + DUMP_TEXT; + + // Other text strings if any. + while (p->in_max > 0) { + DUMP_SPACE; DUMP_TEXT; + } + + DUMP_END; +} + +static int dump_dnskey(DUMP_PARAMS) +{ + if (p->style->wrap) { + char info[512] = ""; + dnskey_info(p->in, p->in_max, info, sizeof(info)); + + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + if (p->style->hide_crypto) { + DUMP_OMIT; + WRAP_LINE; + } else { + WRAP_INIT; + DUMP_BASE64; + WRAP_END; + } + COMMENT(info); + } else { + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + if (p->style->hide_crypto) { + DUMP_KEY_OMIT; + } else { + DUMP_BASE64; + } + } + + DUMP_END; +} + +static int dump_aaaa(DUMP_PARAMS) +{ + DUMP_IPV6; + + DUMP_END; +} + +static int dump_loc(DUMP_PARAMS) +{ + DUMP_LOC; + + DUMP_END; +} + +static int dump_srv(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_DNAME; + + DUMP_END; +} + +static int dump_naptr(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_TEXT; DUMP_SPACE; + DUMP_TEXT; DUMP_SPACE; + DUMP_TEXT; DUMP_SPACE; + DUMP_DNAME; + + DUMP_END; +} + +static int dump_cert(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_BASE64; + WRAP_END; + } else { + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_BASE64; + } + + DUMP_END; +} + +static int dump_apl(DUMP_PARAMS) +{ + // Print list of APLs (empty list is allowed). + while (p->in_max > 0) { + if (p->total > 0) { + DUMP_SPACE; + } + DUMP_APL; + } + + DUMP_END; +} + +static int dump_ds(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_HEX; + WRAP_END; + } else { + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX; + } + + DUMP_END; +} + +static int dump_sshfp(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_HEX; + WRAP_END; + } else { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX; + } + + DUMP_END; +} + +static int dump_ipseckey(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_GATEWAY; + WRAP_END; + } else { + DUMP_NUM8; DUMP_SPACE; + DUMP_GATEWAY; + } + + DUMP_END; +} + +static int dump_rrsig(DUMP_PARAMS) +{ + DUMP_TYPE; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM32; DUMP_SPACE; + DUMP_TIMESTAMP; DUMP_SPACE; + if (p->style->wrap) { + WRAP_INIT; + } + DUMP_TIMESTAMP; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_DNAME; DUMP_SPACE; + if (p->style->wrap) { + WRAP_LINE; + } + if (p->style->hide_crypto) { + DUMP_OMIT; + } else { + DUMP_BASE64; + } + if (p->style->wrap) { + WRAP_END; + } + DUMP_END; +} + +static int dump_nsec(DUMP_PARAMS) +{ + DUMP_DNAME; DUMP_SPACE; + DUMP_BITMAP; + + DUMP_END; +} + +static int dump_dhcid(DUMP_PARAMS) +{ + if (p->style->wrap) { + WRAP_INIT; + DUMP_BASE64; + WRAP_END; + } else { + DUMP_BASE64; + } + + DUMP_END; +} + +static int dump_nsec3(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_SALT; DUMP_SPACE; WRAP_INIT; + DUMP_HASH; DUMP_SPACE; WRAP_LINE; + DUMP_BITMAP; + WRAP_END; + } else { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_SALT; DUMP_SPACE; + DUMP_HASH; DUMP_SPACE; + DUMP_BITMAP; + } + + DUMP_END; +} + +static int dump_nsec3param(DUMP_PARAMS) +{ + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_SALT; + + DUMP_END; +} + +static int dump_tlsa(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_HEX; + WRAP_END; + } else { + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX; + } + + DUMP_END; +} + +static int dump_csync(DUMP_PARAMS) +{ + DUMP_NUM32; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_BITMAP; + + DUMP_END; +} + +static int dump_zonemd(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_NUM32; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_HEX; + WRAP_END; + } else { + DUMP_NUM32; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX; + } + + DUMP_END; +} + +static int dump_l64(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_L64; + + DUMP_END; +} + +static int dump_l32(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_IPV4; + + DUMP_END; +} + +static int dump_eui(DUMP_PARAMS) +{ + DUMP_EUI; + + DUMP_END; +} + +static int dump_tsig(DUMP_PARAMS) +{ + if (p->style->wrap) { + DUMP_DNAME; DUMP_SPACE; + DUMP_NUM48; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; WRAP_INIT; + DUMP_TSIG_DGST; DUMP_SPACE; WRAP_LINE; + DUMP_NUM16; DUMP_SPACE; + DUMP_TSIG_RCODE; DUMP_SPACE; + DUMP_TSIG_DATA; + WRAP_END; + } else { + DUMP_DNAME; DUMP_SPACE; + DUMP_NUM48; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_TSIG_DGST; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_TSIG_RCODE; DUMP_SPACE; + DUMP_TSIG_DATA; + } + + DUMP_END; +} + +static int dump_uri(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_LONG_TEXT; DUMP_SPACE; + + DUMP_END; +} + +static int dump_caa(DUMP_PARAMS) +{ + DUMP_NUM8; DUMP_SPACE; + DUMP_UNQUOTED; DUMP_SPACE; + DUMP_LONG_TEXT; DUMP_SPACE; + + DUMP_END; +} + +static int dump_unknown(DUMP_PARAMS) +{ + if (p->style->wrap) { + WRAP_INIT; + DUMP_UNKNOWN; + WRAP_END; + } else { + DUMP_UNKNOWN; + } + + DUMP_END; +} + +static int txt_dump_data(rrset_dump_params_t *p, uint16_t type) +{ + switch (type) { + case KNOT_RRTYPE_A: + return dump_a(p); + case KNOT_RRTYPE_NS: + case KNOT_RRTYPE_CNAME: + case KNOT_RRTYPE_PTR: + case KNOT_RRTYPE_DNAME: + return dump_ns(p); + case KNOT_RRTYPE_SOA: + return dump_soa(p); + case KNOT_RRTYPE_HINFO: + return dump_hinfo(p); + case KNOT_RRTYPE_MINFO: + case KNOT_RRTYPE_RP: + return dump_minfo(p); + case KNOT_RRTYPE_MX: + case KNOT_RRTYPE_AFSDB: + case KNOT_RRTYPE_RT: + case KNOT_RRTYPE_KX: + case KNOT_RRTYPE_LP: + return dump_mx(p); + case KNOT_RRTYPE_TXT: + case KNOT_RRTYPE_SPF: + return dump_txt(p); + case KNOT_RRTYPE_KEY: + case KNOT_RRTYPE_DNSKEY: + case KNOT_RRTYPE_CDNSKEY: + return dump_dnskey(p); + case KNOT_RRTYPE_AAAA: + return dump_aaaa(p); + case KNOT_RRTYPE_LOC: + return dump_loc(p); + case KNOT_RRTYPE_SRV: + return dump_srv(p); + case KNOT_RRTYPE_NAPTR: + return dump_naptr(p); + case KNOT_RRTYPE_CERT: + return dump_cert(p); + case KNOT_RRTYPE_APL: + return dump_apl(p); + case KNOT_RRTYPE_DS: + case KNOT_RRTYPE_CDS: + return dump_ds(p); + case KNOT_RRTYPE_SSHFP: + return dump_sshfp(p); + case KNOT_RRTYPE_IPSECKEY: + return dump_ipseckey(p); + case KNOT_RRTYPE_RRSIG: + return dump_rrsig(p); + case KNOT_RRTYPE_NSEC: + return dump_nsec(p); + case KNOT_RRTYPE_DHCID: + case KNOT_RRTYPE_OPENPGPKEY: + return dump_dhcid(p); + case KNOT_RRTYPE_NSEC3: + return dump_nsec3(p); + case KNOT_RRTYPE_NSEC3PARAM: + return dump_nsec3param(p); + case KNOT_RRTYPE_TLSA: + case KNOT_RRTYPE_SMIMEA: + return dump_tlsa(p); + case KNOT_RRTYPE_CSYNC: + return dump_csync(p); + case KNOT_RRTYPE_ZONEMD: + return dump_zonemd(p); + case KNOT_RRTYPE_NID: + case KNOT_RRTYPE_L64: + return dump_l64(p); + case KNOT_RRTYPE_L32: + return dump_l32(p); + case KNOT_RRTYPE_EUI48: + case KNOT_RRTYPE_EUI64: + return dump_eui(p); + case KNOT_RRTYPE_TSIG: + return dump_tsig(p); + case KNOT_RRTYPE_URI: + return dump_uri(p); + case KNOT_RRTYPE_CAA: + return dump_caa(p); + default: + return dump_unknown(p); + } +} + +_public_ +int knot_rrset_txt_dump_data(const knot_rrset_t *rrset, + const size_t pos, + char *dst, + const size_t maxlen, + const knot_dump_style_t *style) +{ + if (rrset == NULL || dst == NULL || style == NULL) { + return KNOT_EINVAL; + } + + knot_rdata_t *rr_data = knot_rdataset_at(&rrset->rrs, pos); + if (rr_data == NULL) { + return KNOT_EINVAL; /* bad pos or rrset->rrs */ + } + + uint8_t *data = rr_data->data; + uint16_t data_len = rr_data->len; + + rrset_dump_params_t p = { + .style = style, + .in = data, + .in_max = data_len, + .out = dst, + .out_max = maxlen, + .total = 0, + .ret = 0 + }; + + int ret; + + // Allow empty rdata with the CH class (knsupdate). + if (data_len == 0 && rrset->rclass != KNOT_CLASS_IN) { + ret = 0; + } else if (style->generic) { + ret = dump_unknown(&p); + } else { + ret = txt_dump_data(&p, rrset->type); + } + + // Terminate the string just in case. + if (ret < 0 || ret >= maxlen) { + return KNOT_ESPACE; + } + dst[ret] = '\0'; + + return ret; +} + +_public_ +int knot_rrset_txt_dump_header(const knot_rrset_t *rrset, + const uint32_t ttl, + char *dst, + const size_t maxlen, + const knot_dump_style_t *style) +{ + if (rrset == NULL || dst == NULL || style == NULL) { + return KNOT_EINVAL; + } + + size_t len = 0; + char buf[32]; + int ret; + + // Dump rrset owner. + char *name = knot_dname_to_str_alloc(rrset->owner); + if (style->ascii_to_idn != NULL) { + style->ascii_to_idn(&name); + } + char sep = strlen(name) < 4 * TAB_WIDTH ? '\t' : ' '; + ret = snprintf(dst + len, maxlen - len, "%-20s%c", name, sep); + free(name); + SNPRINTF_CHECK(ret, maxlen - len); + len += ret; + + // Set white space separation character. + sep = style->wrap ? ' ' : '\t'; + + // Dump rrset ttl. + if (style->show_ttl) { + if (style->empty_ttl) { + ret = snprintf(dst + len, maxlen - len, "%c", sep); + } else if (style->human_ttl) { + // Create human readable ttl string. + if (time_to_human_str(buf, sizeof(buf), ttl) < 0) { + return KNOT_ESPACE; + } + ret = snprintf(dst + len, maxlen - len, "%s%c", + buf, sep); + } else { + ret = snprintf(dst + len, maxlen - len, "%u%c", ttl, sep); + } + SNPRINTF_CHECK(ret, maxlen - len); + len += ret; + } + + // Dump rrset class. + if (style->show_class) { + if (knot_rrclass_to_string(rrset->rclass, buf, sizeof(buf)) < 0) { + return KNOT_ESPACE; + } + ret = snprintf(dst + len, maxlen - len, "%-2s%c", buf, sep); + SNPRINTF_CHECK(ret, maxlen - len); + len += ret; + } + + // Dump rrset type. + if (style->generic) { + if (snprintf(buf, sizeof(buf), "TYPE%u", rrset->type) < 0) { + return KNOT_ESPACE; + } + } else if (knot_rrtype_to_string(rrset->type, buf, sizeof(buf)) < 0) { + return KNOT_ESPACE; + } + if (rrset->rrs.count > 0) { + ret = snprintf(dst + len, maxlen - len, "%s%c", buf, sep); + } else { + ret = snprintf(dst + len, maxlen - len, "%s", buf); + } + SNPRINTF_CHECK(ret, maxlen - len); + len += ret; + + return len; +} + +static int rrset_txt_dump(const knot_rrset_t *rrset, + char *dst, + const size_t maxlen, + const knot_dump_style_t *style) +{ + if (rrset == NULL || dst == NULL || style == NULL) { + return KNOT_EINVAL; + } + + size_t len = 0; + + dst[0] = '\0'; + + // Loop over rdata in rrset. + uint16_t rr_count = rrset->rrs.count; + knot_rdata_t *rr = rrset->rrs.rdata; + for (uint16_t i = 0; i < rr_count; i++) { + // Dump rdata owner, class, ttl and type. + uint32_t ttl = ((style->original_ttl && rrset->type == KNOT_RRTYPE_RRSIG) ? + knot_rrsig_original_ttl(rr) : rrset->ttl); + + int ret = knot_rrset_txt_dump_header(rrset, ttl, dst + len, + maxlen - len, style); + if (ret < 0) { + return KNOT_ESPACE; + } + len += ret; + + // Dump rdata as such. + ret = knot_rrset_txt_dump_data(rrset, i, dst + len, + maxlen - len, style); + if (ret < 0) { + return KNOT_ESPACE; + } + len += ret; + + // Terminate line. + if (len >= maxlen - 1) { + return KNOT_ESPACE; + } + dst[len++] = '\n'; + dst[len] = '\0'; + + rr = knot_rdataset_next(rr); + } + + return len; +} + +_public_ +int knot_rrset_txt_dump(const knot_rrset_t *rrset, + char **dst, + size_t *dst_size, + const knot_dump_style_t *style) +{ + if (dst == NULL || dst_size == NULL) { + return KNOT_EINVAL; + } + + while (1) { + int ret = rrset_txt_dump(rrset, *dst, *dst_size, style); + if (ret != KNOT_ESPACE) { + return ret; + } + + size_t new_dst_size = 2 * (*dst_size); + if (new_dst_size > RRSET_DUMP_LIMIT) { + return KNOT_ESPACE; + } + + char * new_dst = malloc(new_dst_size); + if (new_dst == NULL) { + return KNOT_ENOMEM; + } + + free(*dst); + *dst = new_dst; + *dst_size = new_dst_size; + } +} diff --git a/src/libknot/rrset-dump.h b/src/libknot/rrset-dump.h new file mode 100644 index 0000000..02bda71 --- /dev/null +++ b/src/libknot/rrset-dump.h @@ -0,0 +1,113 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief RRset text dump facility. + * + * \addtogroup rr + * @{ + */ + +#pragma once + +#include <stdbool.h> + +#include "libknot/rrset.h" + +/*! \brief Text output settings. */ +typedef struct { + /*!< Wrap long records. */ + bool wrap; + /*!< Show class. */ + bool show_class; + /*!< Show TTL. */ + bool show_ttl; + /*!< Print extra information. */ + bool verbose; + /*!< Print RRSIG original TTL instead of rrset TTL. */ + bool original_ttl; + /*!< Show empty TTL value (keep indentation). */ + bool empty_ttl; + /*!< Format TTL as DHMS. */ + bool human_ttl; + /*!< Format timestamp as YYYYMMDDHHmmSS. */ + bool human_tmstamp; + /*!< Force generic data representation. */ + bool generic; + /*!< Hide binary parts of RRSIGs and DNSKEYs. */ + bool hide_crypto; + /*!< ASCII string to IDN string transformation callback. */ + void (*ascii_to_idn)(char **name); +} knot_dump_style_t; + +/*! \brief Default dump style. */ +extern const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT; + +/*! + * \brief Dumps rrset header. + * + * \param rrset RRset to dump. + * \param ttl TTL to dump. + * \param dst Output buffer. + * \param maxlen Output buffer size. + * \param style Output style. + * + * \retval output length if success. + * \retval < 0 if error. + */ +int knot_rrset_txt_dump_header(const knot_rrset_t *rrset, + const uint32_t ttl, + char *dst, + const size_t maxlen, + const knot_dump_style_t *style); + +/*! + * \brief Dumps rrset data. + * + * \param rrset RRset to dump. + * \param pos Position of the record to dump. + * \param dst Output buffer. + * \param maxlen Output buffer size. + * \param style Output style. + * + * \retval output length if success. + * \retval < 0 if error. + */ +int knot_rrset_txt_dump_data(const knot_rrset_t *rrset, + const size_t pos, + char *dst, + const size_t maxlen, + const knot_dump_style_t *style); + +/*! + * \brief Dumps rrset, re-allocates dst to double (4x, 8x, ...) if too small. + * + * \param rrset RRset to dump. + * \param dst Output buffer. + * \param dst_size Output buffer size (changed if *dst re-allocated). + * \param style Output style. + * + * \retval output length if success. + * \retval < 0 if error. + */ +int knot_rrset_txt_dump(const knot_rrset_t *rrset, + char **dst, + size_t *dst_size, + const knot_dump_style_t *style); + +/*! @} */ diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c new file mode 100644 index 0000000..110422f --- /dev/null +++ b/src/libknot/rrset.c @@ -0,0 +1,217 @@ +/* Copyright (C) 2019 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 <stdbool.h> +#include <stdint.h> + +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/rrset.h" +#include "libknot/rrtype/naptr.h" +#include "libknot/rrtype/rrsig.h" +#include "contrib/mempattern.h" + +_public_ +knot_rrset_t *knot_rrset_new(const knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl, knot_mm_t *mm) +{ + knot_dname_t *owner_cpy = knot_dname_copy(owner, mm); + if (owner_cpy == NULL) { + return NULL; + } + + knot_rrset_t *ret = mm_alloc(mm, sizeof(knot_rrset_t)); + if (ret == NULL) { + knot_dname_free(owner_cpy, mm); + return NULL; + } + + knot_rrset_init(ret, owner_cpy, type, rclass, ttl); + + return ret; +} + +_public_ +knot_rrset_t *knot_rrset_copy(const knot_rrset_t *src, knot_mm_t *mm) +{ + if (src == NULL) { + return NULL; + } + + knot_rrset_t *rrset = knot_rrset_new(src->owner, src->type, + src->rclass, src->ttl, mm); + if (rrset == NULL) { + return NULL; + } + + int ret = knot_rdataset_copy(&rrset->rrs, &src->rrs, mm); + if (ret != KNOT_EOK) { + knot_rrset_free(rrset, mm); + return NULL; + } + + return rrset; +} + +_public_ +void knot_rrset_free(knot_rrset_t *rrset, knot_mm_t *mm) +{ + if (rrset == NULL) { + return; + } + + knot_rrset_clear(rrset, mm); + mm_free(mm, rrset); +} + +_public_ +void knot_rrset_clear(knot_rrset_t *rrset, knot_mm_t *mm) +{ + if (rrset == NULL) { + return; + } + + knot_rdataset_clear(&rrset->rrs, mm); + knot_dname_free(rrset->owner, mm); + rrset->owner = NULL; +} + +_public_ +int knot_rrset_add_rdata(knot_rrset_t *rrset, const uint8_t *data, uint16_t len, + knot_mm_t *mm) +{ + if (rrset == NULL || (data == NULL && len > 0)) { + return KNOT_EINVAL; + } + + uint8_t buf[knot_rdata_size(len)]; + knot_rdata_t *rdata = (knot_rdata_t *)buf; + knot_rdata_init(rdata, len, data); + + return knot_rdataset_add(&rrset->rrs, rdata, mm); +} + +_public_ +bool knot_rrset_equal(const knot_rrset_t *r1, + const knot_rrset_t *r2, + bool incl_ttl) +{ + if (r1->type != r2->type || + (incl_ttl && r1->ttl != r2->ttl)) { + return false; + } + + if ((r1->owner != NULL || r2->owner != NULL) && + !knot_dname_is_equal(r1->owner, r2->owner)) { + return false; + } + + return knot_rdataset_eq(&r1->rrs, &r2->rrs); +} + +_public_ +bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr) +{ + if (rr == NULL) { + return false; + } + + /* Is NSEC3 or non-empty RRSIG covering NSEC3. */ + return ((rr->type == KNOT_RRTYPE_NSEC3) || + (rr->type == KNOT_RRTYPE_RRSIG + && knot_rrsig_type_covered(rr->rrs.rdata) == KNOT_RRTYPE_NSEC3)); +} + +_public_ +int knot_rrset_rr_to_canonical(knot_rrset_t *rrset) +{ + if (rrset == NULL || rrset->rrs.count != 1) { + return KNOT_EINVAL; + } + + /* Convert owner for all RRSets. */ + knot_dname_to_lower(rrset->owner); + + /* Convert DNAMEs in RDATA only for RFC4034 types. */ + if (!knot_rrtype_should_be_lowercased(rrset->type)) { + return KNOT_EOK; + } + + const knot_rdata_descriptor_t *desc = knot_get_rdata_descriptor(rrset->type); + if (desc->type_name == NULL) { + desc = knot_get_obsolete_rdata_descriptor(rrset->type); + } + + uint16_t rdlen = rrset->rrs.rdata->len; + uint8_t *pos = rrset->rrs.rdata->data; + uint8_t *endpos = pos + rdlen; + + /* No RDATA */ + if (rdlen == 0) { + return KNOT_EOK; + } + + /* Otherwise, whole and not malformed RDATA are expected. */ + for (int i = 0; desc->block_types[i] != KNOT_RDATA_WF_END; ++i) { + int type = desc->block_types[i]; + switch (type) { + case KNOT_RDATA_WF_COMPRESSIBLE_DNAME: + case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME: + case KNOT_RDATA_WF_FIXED_DNAME: + knot_dname_to_lower(pos); + pos += knot_dname_size(pos); + break; + case KNOT_RDATA_WF_NAPTR_HEADER: + ; int ret = knot_naptr_header_size(pos, endpos); + if (ret < 0) { + return ret; + } + + pos += ret; + break; + case KNOT_RDATA_WF_REMAINDER: + break; + default: + /* Fixed size block */ + assert(type > 0); + pos += type; + } + } + + return KNOT_EOK; +} + +_public_ +size_t knot_rrset_size(const knot_rrset_t *rrset) +{ + if (rrset == NULL) { + return 0; + } + + uint16_t rr_count = rrset->rrs.count; + + size_t total_size = knot_dname_size(rrset->owner) * rr_count; + + knot_rdata_t *rr = rrset->rrs.rdata; + for (size_t i = 0; i < rr_count; ++i) { + /* 10B = TYPE + CLASS + TTL + RDLENGTH */ + total_size += rr->len + 10; + rr = knot_rdataset_next(rr); + } + + return total_size; +} diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h new file mode 100644 index 0000000..fdc5719 --- /dev/null +++ b/src/libknot/rrset.h @@ -0,0 +1,194 @@ +/* Copyright (C) 2019 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/>. + */ + +/*! + * \file + * + * \brief RRSet structure and API for manipulating it. + * + * \addtogroup rr + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include "libknot/dname.h" +#include "libknot/descriptor.h" +#include "libknot/mm_ctx.h" +#include "libknot/rdataset.h" + +/*! + * \brief Structure for representing RRSet. + * + * For RRSet definition see RFC2181, Section 5. + */ +typedef struct { + knot_dname_t *owner; /*!< Domain name being the owner of the RRSet. */ + uint32_t ttl; /*!< TTL of the RRset. */ + uint16_t type; /*!< TYPE of the RRset. */ + uint16_t rclass; /*!< CLASS of the RRSet. */ + knot_rdataset_t rrs; /*!< RRSet's RRs */ + /* Optional fields. */ + void *additional; /*!< Additional records. */ +} knot_rrset_t; + +/*! + * \brief Creates a new RRSet with the given properties. + * + * The created RRSet contains no RDATAs (i.e. is actually empty). + * + * \param owner OWNER of the RRSet. + * \param type TYPE of the RRSet. + * \param rclass CLASS of the RRSet. + * \param ttl TTL of the RRSet. + * \param mm Memory context. + * + * \return New RRSet structure or NULL if an error occurred. + */ +knot_rrset_t *knot_rrset_new(const knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl, knot_mm_t *mm); + +/*! + * \brief Initializes RRSet structure with given data. + * + * \param rrset RRSet to init. + * \param owner RRSet owner to use. + * \param type RR type to use. + * \param rclass Class to use. + * \param ttl TTL to use. + */ +inline static void knot_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl) +{ + if (rrset != NULL) { + rrset->owner = owner; + rrset->type = type; + rrset->rclass = rclass; + rrset->ttl = ttl; + knot_rdataset_init(&rrset->rrs); + rrset->additional = NULL; + } +} + +/*! + * \brief Initializes given RRSet structure. + * + * \param rrset RRSet to init. + */ +inline static void knot_rrset_init_empty(knot_rrset_t *rrset) +{ + knot_rrset_init(rrset, NULL, 0, KNOT_CLASS_IN, 0); +} + +/*! + * \brief Creates new RRSet from \a src RRSet. + * + * \param src Source RRSet. + * \param mm Memory context. + * + * \retval Pointer to new RRSet if all went OK. + * \retval NULL on error. + */ +knot_rrset_t *knot_rrset_copy(const knot_rrset_t *src, knot_mm_t *mm); + +/*! + * \brief Destroys the RRSet structure and all its substructures. + * + * \param rrset RRset to be destroyed. + * \param mm Memory context. + */ +void knot_rrset_free(knot_rrset_t *rrset, knot_mm_t *mm); + +/*! + * \brief Frees structures inside RRSet, but not the RRSet itself. + * + * \param rrset RRSet to be cleared. + * \param mm Memory context used for allocations. + */ +void knot_rrset_clear(knot_rrset_t *rrset, knot_mm_t *mm); + +/*! + * \brief Adds the given RDATA to the RRSet. + * + * \param rrset RRSet to add the RDATA to. + * \param data RDATA to add to the RRSet. + * \param len Length of RDATA. + * \param mm Memory context. + * + * \return KNOT_E* + */ +int knot_rrset_add_rdata(knot_rrset_t *rrset, const uint8_t *data, uint16_t len, + knot_mm_t *mm); + +/*! + * \brief Compares two RRSets for equality. + * + * \param r1 First RRSet. + * \param r2 Second RRSet. + * \param incl_ttl Compare also TTLs for equality. + * + * \retval True if RRSets are equal. + * \retval False if RRSets are not equal. + */ +bool knot_rrset_equal(const knot_rrset_t *r1, const knot_rrset_t *r2, + bool incl_ttl); + +/*! + * \brief Checks whether RRSet is empty. + * + * \param rrset RRSet to check. + * + * \retval True if RRSet is empty. + * \retval False if RRSet is not empty. + */ +inline static bool knot_rrset_empty(const knot_rrset_t *rrset) +{ + return rrset == NULL || rrset->rrs.count == 0; +} + +/*! + * \brief Return whether the RR type is NSEC3 related (NSEC3 or RRSIG). + */ +bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr); + +/*! + * \brief Convert one RR into canonical format. + * + * Owner is always converted to lowercase. RDATA domain names are converted only + * for types listed in RFC 4034, Section 6.2, except for NSEC (updated by + * RFC 6840, Section 5.1) and A6 (not supported). + * + * \note If RRSet with more RRs is given to this function, only the first RR + * will be converted. + * \warning This function expects either empty RDATA or full, not malformed + * RDATA. If malformed RRSet is passed to this function, memory errors + * may occur. + * + * \param rrset RR to convert. + */ +int knot_rrset_rr_to_canonical(knot_rrset_t *rrset); + +/*! + * \brief Size of rrset in wire format. + * + * \retval size in bytes + */ +size_t knot_rrset_size(const knot_rrset_t *rrset); + +/*! @} */ diff --git a/src/libknot/rrtype/dnskey.h b/src/libknot/rrtype/dnskey.h new file mode 100644 index 0000000..229b353 --- /dev/null +++ b/src/libknot/rrtype/dnskey.h @@ -0,0 +1,72 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/rdata.h" +#include "libknot/wire.h" + +/*! See https://www.iana.org/assignments/dnskey-flags */ +/*! /brief "Secure entry point" marks KSK and CSK in practice. */ +#define KNOT_DNSKEY_FLAG_SEP 1 +/*! /brief The key is ALLOWED to be used for zone contents signing. */ +#define KNOT_DNSKEY_FLAG_ZONE 256 +/*! /brief The key MUST NOT be used for validation. */ +#define KNOT_DNSKEY_FLAG_REVOKE 128 + +static inline +uint16_t knot_dnskey_flags(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data); +} + +static inline +uint8_t knot_dnskey_proto(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 2); +} + +static inline +uint8_t knot_dnskey_alg(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 3); +} + +static inline +uint16_t knot_dnskey_key_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->len - 4; +} + +static inline +const uint8_t *knot_dnskey_key(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 4; +} + +/*! @} */ diff --git a/src/libknot/rrtype/ds.h b/src/libknot/rrtype/ds.h new file mode 100644 index 0000000..cb82e08 --- /dev/null +++ b/src/libknot/rrtype/ds.h @@ -0,0 +1,64 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/rdata.h" +#include "libknot/wire.h" + +static inline +uint16_t knot_ds_key_tag(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data); +} + +static inline +uint8_t knot_ds_alg(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 2); +} + +static inline +uint8_t knot_ds_digest_type(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 3); +} + +static inline +uint16_t knot_ds_digest_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->len - 4; +} + +static inline +const uint8_t *knot_ds_digest(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 4; +} + +/*! @} */ diff --git a/src/libknot/rrtype/naptr.c b/src/libknot/rrtype/naptr.c new file mode 100644 index 0000000..9307816 --- /dev/null +++ b/src/libknot/rrtype/naptr.c @@ -0,0 +1,47 @@ +/* Copyright (C) 2015 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 <stdint.h> + +#include "libknot/attribute.h" +#include "libknot/rrtype/naptr.h" +#include "contrib/wire_ctx.h" + +_public_ +int knot_naptr_header_size(const uint8_t *naptr, const uint8_t *maxp) +{ + if (!naptr || !maxp || naptr >= maxp) { + return KNOT_EINVAL; + } + + wire_ctx_t wire = wire_ctx_init_const(naptr, maxp - naptr); + + /* Fixed fields size (order, preference) */ + wire_ctx_skip(&wire, 2 * sizeof(uint16_t)); + + /* Variable fields size (flags, services, regexp) */ + for (int i = 0; i < 3; i++) { + uint8_t size = wire_ctx_read_u8(&wire); + wire_ctx_skip(&wire, size); + } + + if (wire.error != KNOT_EOK) { + return KNOT_EMALF; + } + + return wire_ctx_offset(&wire); +} diff --git a/src/libknot/rrtype/naptr.h b/src/libknot/rrtype/naptr.h new file mode 100644 index 0000000..ab14a9f --- /dev/null +++ b/src/libknot/rrtype/naptr.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include <stdint.h> + +/*! + * \brief Counts the size of the NAPTR RDATA before the Replacement domain name. + * + * See RFC 2915. + * + * \param naptr Wire format of NAPTR record. + * \param maxp Limit of the wire format. + * + * \retval KNOT_EMALF if the record is malformed. + * \retval Size of the RDATA before the Replacement domain name. + */ +int knot_naptr_header_size(const uint8_t *naptr, const uint8_t *maxp); + +/*! @} */ diff --git a/src/libknot/rrtype/nsec.h b/src/libknot/rrtype/nsec.h new file mode 100644 index 0000000..be1ce31 --- /dev/null +++ b/src/libknot/rrtype/nsec.h @@ -0,0 +1,50 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/dname.h" +#include "libknot/rdata.h" + +static inline +const knot_dname_t *knot_nsec_next(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +uint16_t knot_nsec_bitmap_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->len - knot_dname_size(knot_nsec_next(rdata)); +} + +static inline +const uint8_t *knot_nsec_bitmap(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + knot_dname_size(knot_nsec_next(rdata)); +} + +/*! @} */ diff --git a/src/libknot/rrtype/nsec3.h b/src/libknot/rrtype/nsec3.h new file mode 100644 index 0000000..5bb94bd --- /dev/null +++ b/src/libknot/rrtype/nsec3.h @@ -0,0 +1,98 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/rdata.h" +#include "libknot/wire.h" + +/*! + * \brief NSEC3 rdata constants. + */ +#define KNOT_NSEC3_ALGORITHM_SHA1 1 +#define KNOT_NSEC3_FLAG_OPT_OUT 1 + +static inline +uint8_t knot_nsec3_alg(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data); +} + +static inline +uint8_t knot_nsec3_flags(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 1); +} + +static inline +uint16_t knot_nsec3_iters(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data + 2); +} + +static inline +uint8_t knot_nsec3_salt_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 4); +} + +static inline +const uint8_t *knot_nsec3_salt(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 5; +} + +static inline +uint8_t knot_nsec3_next_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 5 + knot_nsec3_salt_len(rdata)); +} + +static inline +const uint8_t *knot_nsec3_next(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 6 + knot_nsec3_salt_len(rdata); +} + +static inline +uint16_t knot_nsec3_bitmap_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->len - 6 - knot_nsec3_salt_len(rdata) - knot_nsec3_next_len(rdata); +} + +static inline +const uint8_t *knot_nsec3_bitmap(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 6 + knot_nsec3_salt_len(rdata) + knot_nsec3_next_len(rdata); +} + +/*! @} */ diff --git a/src/libknot/rrtype/nsec3param.h b/src/libknot/rrtype/nsec3param.h new file mode 100644 index 0000000..43bce18 --- /dev/null +++ b/src/libknot/rrtype/nsec3param.h @@ -0,0 +1,64 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/rdata.h" +#include "libknot/wire.h" + +static inline +uint8_t knot_nsec3param_alg(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data); +} + +static inline +uint8_t knot_nsec3param_flags(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 1); +} + +static inline +uint16_t knot_nsec3param_iters(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data + 2); +} + +static inline +uint8_t knot_nsec3param_salt_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 4); +} + +static inline +const uint8_t *knot_nsec3param_salt(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 5; +} + +/*! @} */ diff --git a/src/libknot/rrtype/opt.c b/src/libknot/rrtype/opt.c new file mode 100644 index 0000000..b9a8c30 --- /dev/null +++ b/src/libknot/rrtype/opt.c @@ -0,0 +1,721 @@ +/* Copyright (C) 2020 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 <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/lookup.h" +#include "libknot/packet/pkt.h" +#include "libknot/rrtype/opt.h" +#include "contrib/mempattern.h" +#include "contrib/wire_ctx.h" + +/*! \brief Some implementation-related constants. */ +enum { + /*! \brief Byte offset of the extended RCODE field in TTL. */ + EDNS_OFFSET_RCODE = 0, + /*! \brief Byte offset of the version field in TTL. */ + EDNS_OFFSET_VERSION = 1, + + /*! \brief Byte offset of the family field in option data. */ + EDNS_OFFSET_CLIENT_SUBNET_FAMILY = 0, + /*! \brief Byte offset of the source mask field in option data. */ + EDNS_OFFSET_CLIENT_SUBNET_SRC_MASK = 2, + /*! \brief Byte offset of the destination mask field in option data. */ + EDNS_OFFSET_CLIENT_SUBNET_DST_MASK = 3, + /*! \brief Byte offset of the address field in option data. */ + EDNS_OFFSET_CLIENT_SUBNET_ADDR = 4, +}; + +_public_ +int knot_edns_init(knot_rrset_t *opt_rr, uint16_t max_pld, + uint8_t ext_rcode, uint8_t ver, knot_mm_t *mm) +{ + if (opt_rr == NULL) { + return KNOT_EINVAL; + } + + /* Initialize RRSet. */ + knot_dname_t *owner = knot_dname_copy((const uint8_t *)"", mm); + if (owner == NULL) { + return KNOT_ENOMEM; + } + + knot_rrset_init(opt_rr, owner, KNOT_RRTYPE_OPT, max_pld, 0); + + /* Create empty RDATA */ + int ret = knot_rrset_add_rdata(opt_rr, NULL, 0, mm); + if (ret == KNOT_EOK) { + knot_edns_set_ext_rcode(opt_rr, ext_rcode); + knot_edns_set_version(opt_rr, ver); + } + + return ret; +} + +_public_ +uint8_t knot_edns_get_ext_rcode(const knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + uint32_t ttl = 0; + wire_ctx_t w = wire_ctx_init((uint8_t *)&ttl, sizeof(ttl)); + // TTL is stored in machine byte order. Convert it to wire order first. + wire_ctx_write_u32(&w, opt_rr->ttl); + wire_ctx_set_offset(&w, EDNS_OFFSET_RCODE); + return wire_ctx_read_u8(&w); +} + +static void set_value_to_ttl(knot_rrset_t *opt_rr, size_t offset, uint8_t value) +{ + uint32_t ttl = 0; + wire_ctx_t w = wire_ctx_init((uint8_t *)&ttl, sizeof(ttl)); + // TTL is stored in machine byte order. Convert it to wire order first. + wire_ctx_write_u32(&w, opt_rr->ttl); + // Set the Extended RCODE in the converted TTL + wire_ctx_set_offset(&w, offset); + wire_ctx_write_u8(&w, value); + // Convert it back to machine byte order + wire_ctx_set_offset(&w, 0); + uint32_t ttl_local = wire_ctx_read_u32(&w); + // Store the TTL to the RDATA + opt_rr->ttl = ttl_local; +} + +_public_ +void knot_edns_set_ext_rcode(knot_rrset_t *opt_rr, uint8_t ext_rcode) +{ + assert(opt_rr != NULL); + set_value_to_ttl(opt_rr, EDNS_OFFSET_RCODE, ext_rcode); +} + +_public_ +uint8_t knot_edns_get_version(const knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + uint32_t ttl = 0; + wire_ctx_t w = wire_ctx_init((uint8_t *)&ttl, sizeof(ttl)); + // TTL is stored in machine byte order. Convert it to wire order first. + wire_ctx_write_u32(&w, opt_rr->ttl); + wire_ctx_set_offset(&w, EDNS_OFFSET_VERSION); + return wire_ctx_read_u8(&w); +} + +_public_ +void knot_edns_set_version(knot_rrset_t *opt_rr, uint8_t version) +{ + assert(opt_rr != NULL); + set_value_to_ttl(opt_rr, EDNS_OFFSET_VERSION, version); +} + +/*! + * \brief Add new EDNS option by replacing RDATA of OPT RR. + * + * \param opt OPT RR structure to add the Option to. + * \param code Option code. + * \param size Option data length in bytes. + * \param mm Memory context. + * + * \return Pointer to uninitialized option data. + */ +static uint8_t *edns_add(knot_rrset_t *opt, uint16_t code, uint16_t size, + knot_mm_t *mm) +{ + assert(opt->rrs.count == 1); + + // extract old RDATA + + uint8_t *old_data = opt->rrs.rdata->data; + uint16_t old_data_len = opt->rrs.rdata->len; + + // construct new RDATA + + uint16_t new_data_len = old_data_len + KNOT_EDNS_OPTION_HDRLEN + size; + uint8_t new_data[new_data_len]; + + wire_ctx_t wire = wire_ctx_init(new_data, new_data_len); + wire_ctx_write(&wire, old_data, old_data_len); + wire_ctx_write_u16(&wire, code); + wire_ctx_write_u16(&wire, size); + + // prepare EDNS option data + + size_t offset = wire_ctx_offset(&wire); + wire_ctx_clear(&wire, size); + + assert(wire_ctx_available(&wire) == 0); + assert(wire.error == KNOT_EOK); + + // replace RDATA + + knot_rdataset_clear(&opt->rrs, mm); + if (knot_rrset_add_rdata(opt, new_data, new_data_len, mm) != KNOT_EOK) { + return NULL; + } + + return opt->rrs.rdata->data + offset; +} + +_public_ +int knot_edns_reserve_option(knot_rrset_t *opt_rr, uint16_t code, + uint16_t size, uint8_t **wire_ptr, knot_mm_t *mm) +{ + if (!opt_rr) { + return KNOT_EINVAL; + } + + uint8_t *wire = edns_add(opt_rr, code, size, mm); + if (!wire) { + return KNOT_ENOMEM; + } + + if (wire_ptr) { + *wire_ptr = wire; + } + + return KNOT_EOK; +} + +_public_ +int knot_edns_add_option(knot_rrset_t *opt_rr, uint16_t code, + uint16_t size, const uint8_t *data, knot_mm_t *mm) +{ + if (!opt_rr || (size > 0 && !data)) { + return KNOT_EINVAL; + } + + uint8_t *wire = edns_add(opt_rr, code, size, mm); + if (!wire) { + return KNOT_ENOMEM; + } + + memcpy(wire, data, size); + + return KNOT_EOK; +} + +_public_ +uint8_t *knot_edns_get_option(const knot_rrset_t *opt_rr, uint16_t code, + const uint8_t *previous) +{ + if (opt_rr == NULL) { + return NULL; + } + + knot_rdata_t *rdata = opt_rr->rrs.rdata; + if (rdata == NULL || rdata->len == 0) { + return NULL; + } + + wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len); + if (previous != NULL) { + if (previous < wire.wire) { + return NULL; + } + wire_ctx_set_offset(&wire, previous - wire.wire + 2); + uint16_t opt_len = wire_ctx_read_u16(&wire); + wire_ctx_skip(&wire, opt_len); + } + + 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 NULL; +} + +_public_ +int knot_edns_get_options(knot_rrset_t *opt_rr, knot_edns_options_t **out, + knot_mm_t *mm) +{ + if (opt_rr == NULL || opt_rr->rrs.count > 1 || out == NULL) { + return KNOT_EINVAL; + } + + knot_rdata_t *rdata = opt_rr->rrs.rdata; + 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); + + 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 <= KNOT_EDNS_MAX_OPTION_CODE) { + options->ptr[opt_code] = pos; + } + } + + if (wire.error != KNOT_EOK) { + mm_free(mm, options); + return wire.error; + } + + *out = options; + return KNOT_EOK; +} + +_public_ +int knot_edns_alignment_size(size_t current_pkt_size, + size_t current_opt_size, + size_t block_size) +{ + if (current_opt_size == 0 || block_size == 0) { + return -1; + } + + size_t current_size = current_pkt_size + current_opt_size; + if (current_size % block_size == 0) { + return -1; + } + + size_t modulo = (current_size + KNOT_EDNS_OPTION_HDRLEN) % block_size; + + return (modulo == 0) ? 0 : block_size - modulo; +} + +/*! + * \brief EDNS Client Subnet family data. + */ +typedef struct { + int platform; //!< Platform family identifier. + uint16_t iana; //!< IANA family identifier. + size_t offset; //!< Socket address offset. + size_t size; //!< Socket address size. +} ecs_family_t; + +#define ECS_INIT(platform, iana, type, member) \ + { platform, iana, offsetof(type, member), sizeof(((type *)0)->member) } + +/*! + * \brief Supported EDNS Client Subnet families. + * + * https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xml + */ +static const ecs_family_t ECS_FAMILIES[] = { + ECS_INIT(AF_INET, KNOT_ADDR_FAMILY_IPV4, struct sockaddr_in, sin_addr), + ECS_INIT(AF_INET6, KNOT_ADDR_FAMILY_IPV6, struct sockaddr_in6, sin6_addr), + { 0 } +}; + +/*! + * \brief Lookup ECS family by platform identifier. + */ +static const ecs_family_t *ecs_family_by_platform(int family) +{ + for (const ecs_family_t *f = ECS_FAMILIES; f->size > 0; f++) { + if (f->platform == family) { + return f; + } + } + + return NULL; +} + +/*! + * \brief Lookup ECS family by IANA identifier. + */ +static const ecs_family_t *ecs_family_by_iana(uint16_t family) +{ + for (const ecs_family_t *f = ECS_FAMILIES; f->size > 0; f++) { + if (f->iana == family) { + return f; + } + } + + return NULL; +} + +/*! + * \brief Get ECS address prefix size in bytes. + */ +static uint16_t ecs_prefix_size(uint8_t prefix) +{ + return (prefix + 7) / 8; +} + +static uint8_t ecs_prefix_lsb_mask(uint8_t prefix) +{ + int modulo = prefix % 8; + if (modulo == 0) { + return 0xff; + } else { + return 0xff << (8 - modulo); + } +} + +/*! + * \brief Write raw network address prefix and clear the rest of the buffer. + */ +static void ecs_write_address(wire_ctx_t *dst, wire_ctx_t *src, int8_t prefix) +{ + size_t count = ecs_prefix_size(prefix); + uint8_t lsb_mask = ecs_prefix_lsb_mask(prefix); + + if (count > 0) { + wire_ctx_copy(dst, src, count); + if (dst->error != KNOT_EOK) { + return; + } + dst->position[-1] &= lsb_mask; + } + + size_t blank = wire_ctx_available(dst); + wire_ctx_clear(dst, blank); +} + +/*! + * \brief Check if ECS parameters are valid. + */ +static bool ecs_is_valid(const knot_edns_client_subnet_t *ecs) +{ + if (ecs == NULL) { + return false; + } + + const ecs_family_t *f = ecs_family_by_iana(ecs->family); + + return f != NULL && // known family check + (ecs->source_len <= f->size * 8) && // family address maximum check + (ecs->scope_len <= f->size * 8); // family address maximum check +} + +_public_ +uint16_t knot_edns_client_subnet_size(const knot_edns_client_subnet_t *ecs) +{ + if (!ecs_is_valid(ecs)) { + return 0; + } + + return sizeof(ecs->family) + + sizeof(ecs->source_len) + + sizeof(ecs->scope_len) + + ecs_prefix_size(ecs->source_len); +} + +_public_ +int knot_edns_client_subnet_write(uint8_t *option, uint16_t option_len, + const knot_edns_client_subnet_t *ecs) +{ + if (option == NULL || ecs == NULL) { + return KNOT_EINVAL; + } + + if (!ecs_is_valid(ecs)) { + return KNOT_EINVAL; + } + + wire_ctx_t wire = wire_ctx_init(option, option_len); + wire_ctx_t addr = wire_ctx_init_const(ecs->address, sizeof(ecs->address)); + + wire_ctx_write_u16(&wire, ecs->family); + wire_ctx_write_u8(&wire, ecs->source_len); + wire_ctx_write_u8(&wire, ecs->scope_len); + ecs_write_address(&wire, &addr, ecs->source_len); + + if (wire.error != KNOT_EOK) { + return wire.error; + } + + return KNOT_EOK; +} + +_public_ +int knot_edns_client_subnet_parse(knot_edns_client_subnet_t *ecs, + const uint8_t *option, uint16_t option_len) +{ + if (ecs == NULL || option == NULL) { + return KNOT_EINVAL; + } + + knot_edns_client_subnet_t result = { 0 }; + + wire_ctx_t wire = wire_ctx_init_const(option, option_len); + wire_ctx_t addr = wire_ctx_init(result.address, sizeof(result.address)); + + result.family = wire_ctx_read_u16(&wire); + result.source_len = wire_ctx_read_u8(&wire); + result.scope_len = wire_ctx_read_u8(&wire); + ecs_write_address(&addr, &wire, result.source_len); + + if (addr.error != KNOT_EOK || wire.error != KNOT_EOK) { + return KNOT_EMALF; + } + + if (!ecs_is_valid(&result)) { + return KNOT_EMALF; + } + + *ecs = result; + return KNOT_EOK; +} + +_public_ +int knot_edns_client_subnet_set_addr(knot_edns_client_subnet_t *ecs, + const struct sockaddr_storage *addr) +{ + if (ecs == NULL || addr == NULL) { + return KNOT_EINVAL; + } + + const ecs_family_t *f = ecs_family_by_platform(addr->ss_family); + if (f == NULL) { + return KNOT_ENOTSUP; + } + + ecs->family = f->iana; + ecs->source_len = f->size * 8; + ecs->scope_len = 0; + + wire_ctx_t dst = wire_ctx_init(ecs->address, sizeof(ecs->address)); + wire_ctx_t src = wire_ctx_init_const((uint8_t *)addr + f->offset, f->size); + ecs_write_address(&dst, &src, ecs->source_len); + + assert(dst.error == KNOT_EOK); + + return KNOT_EOK; +} + +_public_ +int knot_edns_client_subnet_get_addr(struct sockaddr_storage *addr, + const knot_edns_client_subnet_t *ecs) +{ + if (addr == NULL || ecs == NULL) { + return KNOT_EINVAL; + } + + const ecs_family_t *f = ecs_family_by_iana(ecs->family); + if (f == NULL) { + return KNOT_ENOTSUP; + } + + addr->ss_family = f->platform; + + wire_ctx_t dst = wire_ctx_init((uint8_t *)addr + f->offset, f->size); + wire_ctx_t src = wire_ctx_init_const(ecs->address, sizeof(ecs->address)); + ecs_write_address(&dst, &src, ecs->source_len); + + assert(dst.error == KNOT_EOK); + + return KNOT_EOK; +} + +_public_ +uint16_t knot_edns_keepalive_size(uint16_t timeout) +{ + return (timeout > 0) ? sizeof(uint16_t) : 0; +} + +_public_ +int knot_edns_keepalive_write(uint8_t *option, uint16_t option_len, uint16_t timeout) +{ + if (option == NULL) { + return KNOT_EINVAL; + } + + if (timeout == 0) { + return KNOT_EOK; + } + + wire_ctx_t wire = wire_ctx_init(option, option_len); + wire_ctx_write_u16(&wire, timeout); + + return wire.error; +} + +_public_ +int knot_edns_keepalive_parse(uint16_t *timeout, const uint8_t *option, + uint16_t option_len) +{ + if (timeout == NULL || option == NULL) { + return KNOT_EINVAL; + } + + *timeout = 0; + + if (option_len > 0) { + wire_ctx_t wire = wire_ctx_init_const(option, option_len); + *timeout = wire_ctx_read_u16(&wire); + + if (wire.error != KNOT_EOK) { + return KNOT_EMALF; + } + } + + return KNOT_EOK; +} + +_public_ +uint16_t knot_edns_chain_size(const knot_dname_t *point) +{ + return knot_dname_size(point); +} + +_public_ +int knot_edns_chain_write(uint8_t *option, uint16_t option_len, + const knot_dname_t *point) +{ + if (option == NULL || point == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_t wire = wire_ctx_init(option, option_len); + wire_ctx_write(&wire, point, knot_dname_size(point)); + + return wire.error; +} + +_public_ +int knot_edns_chain_parse(knot_dname_t **point, const uint8_t *option, + uint16_t option_len, knot_mm_t *mm) +{ + if (point == NULL || option == NULL) { + return KNOT_EINVAL; + } + + int ret = knot_dname_wire_check(option, option + option_len, NULL); + if (ret <= 0) { + return KNOT_EMALF; + } + + *point = knot_dname_copy(option, mm); + if (*point == NULL) { + return KNOT_ENOMEM; + } + + return KNOT_EOK; +} + +_public_ +uint16_t knot_edns_cookie_size(const knot_edns_cookie_t *cc, + const knot_edns_cookie_t *sc) +{ + if (cc == NULL || cc->len != KNOT_EDNS_COOKIE_CLNT_SIZE) { + return 0; + } else if (sc == NULL || sc->len == 0) { + return KNOT_EDNS_COOKIE_CLNT_SIZE; + } else if (sc->len < KNOT_EDNS_COOKIE_SRVR_MIN_SIZE || + sc->len > KNOT_EDNS_COOKIE_SRVR_MAX_SIZE) { + return 0; + } else { + return cc->len + sc->len; + } +} + +_public_ +int knot_edns_cookie_write(uint8_t *option, uint16_t option_len, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_t *sc) +{ + if (option == NULL || cc == NULL || cc->len != KNOT_EDNS_COOKIE_CLNT_SIZE) { + return KNOT_EINVAL; + } + + wire_ctx_t wire = wire_ctx_init(option, option_len); + wire_ctx_write(&wire, cc->data, cc->len); + + if (sc != NULL && sc->len > 0) { + if (sc->len < KNOT_EDNS_COOKIE_SRVR_MIN_SIZE || + sc->len > KNOT_EDNS_COOKIE_SRVR_MAX_SIZE) { + return KNOT_EINVAL; + } + wire_ctx_write(&wire, sc->data, sc->len); + } + + return wire.error; +} + +_public_ +int knot_edns_cookie_parse(knot_edns_cookie_t *cc, knot_edns_cookie_t *sc, + const uint8_t *option, uint16_t option_len) +{ + if (cc == NULL || sc == NULL || option == NULL) { + return KNOT_EINVAL; + } + + if (option_len != KNOT_EDNS_COOKIE_CLNT_SIZE && + (option_len < KNOT_EDNS_COOKIE_CLNT_SIZE + KNOT_EDNS_COOKIE_SRVR_MIN_SIZE || + option_len > KNOT_EDNS_COOKIE_CLNT_SIZE + KNOT_EDNS_COOKIE_SRVR_MAX_SIZE)) { + return KNOT_EMALF; + } + assert(option_len >= KNOT_EDNS_COOKIE_CLNT_SIZE); + + memcpy(cc->data, option, KNOT_EDNS_COOKIE_CLNT_SIZE); + cc->len = KNOT_EDNS_COOKIE_CLNT_SIZE; + + size_t sc_len = option_len - KNOT_EDNS_COOKIE_CLNT_SIZE; + if (sc_len == 0) { + sc->len = 0; + } else { + memcpy(sc->data, option + KNOT_EDNS_COOKIE_CLNT_SIZE, sc_len); + sc->len = sc_len; + } + + return KNOT_EOK; +} + +static const knot_lookup_t ext_errors[] = { + { KNOT_EDNS_EDE_OTHER, "Other" }, + { KNOT_EDNS_EDE_DNSKEY_ALG, "Unsupported DNSKEY Algorithm" }, + { KNOT_EDNS_EDE_DS_DIGEST, "Unsupported DS Digest Type" }, + { KNOT_EDNS_EDE_STALE, "Stale Answer" }, + { KNOT_EDNS_EDE_FORGED, "Forged Answer" }, + { KNOT_EDNS_EDE_INDETERMINATE, "DNSSEC Indeterminate" }, + { KNOT_EDNS_EDE_BOGUS, "DNSSEC Bogus" }, + { KNOT_EDNS_EDE_SIG_EXPIRED, "Signature Expired" }, + { KNOT_EDNS_EDE_SIG_NOTYET, "Signature Not Yet Valid" }, + { KNOT_EDNS_EDE_DNSKEY_MISS, "DNSKEY Missing" }, + { KNOT_EDNS_EDE_RRSIG_MISS, "RRSIGs Missing" }, + { KNOT_EDNS_EDE_DNSKEY_BIT, "No Zone Key Bit Set" }, + { KNOT_EDNS_EDE_NSEC_MISS, "NSEC Missing" }, + { KNOT_EDNS_EDE_CACHED_ERR, "Cached Error" }, + { KNOT_EDNS_EDE_NOT_READY, "Not Ready" }, + { KNOT_EDNS_EDE_BLOCKED, "Blocked" }, + { KNOT_EDNS_EDE_CENSORED, "Censored" }, + { KNOT_EDNS_EDE_FILTERED, "Filtered" }, + { KNOT_EDNS_EDE_PROHIBITED, "Prohibited" }, + { KNOT_EDNS_EDE_STALE_NXD, "Stale NXDOMAIN Answer" }, + { KNOT_EDNS_EDE_NOTAUTH, "Not Authoritative" }, + { KNOT_EDNS_EDE_NOTSUP, "Not Supported" }, + { KNOT_EDNS_EDE_NREACH_AUTH, "No Reachable Authority" }, + { KNOT_EDNS_EDE_NETWORK, "Network Error" }, + { KNOT_EDNS_EDE_INV_DATA, "Invalid Data" }, + { 0, NULL } +}; + +_public_ +const char *knot_edns_ede_strerr(uint16_t ede_code) +{ + const knot_lookup_t *ede = knot_lookup_by_id(ext_errors, ede_code); + return (ede != NULL ? ede->name : "Unknown Code"); +} diff --git a/src/libknot/rrtype/opt.h b/src/libknot/rrtype/opt.h new file mode 100644 index 0000000..667d526 --- /dev/null +++ b/src/libknot/rrtype/opt.h @@ -0,0 +1,622 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \brief Functions for manipulating the EDNS OPT pseudo-RR. + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include <assert.h> +#include <sys/socket.h> + +#include "libknot/consts.h" +#include "libknot/rrset.h" +#include "libknot/wire.h" + +/*! \brief Constants related to EDNS. */ +enum { + /*! \brief Supported EDNS version. */ + KNOT_EDNS_VERSION = 0, + + /*! \brief Bit mask for DO bit. */ + KNOT_EDNS_DO_MASK = (uint32_t)(1 << 15), + + /*! \brief Minimal UDP payload with EDNS enabled. */ + KNOT_EDNS_MIN_UDP_PAYLOAD = 512, + /*! \brief Minimal payload when using DNSSEC (RFC4035/sec.3). */ + KNOT_EDNS_MIN_DNSSEC_PAYLOAD = 1220, + /*! \brief Maximal UDP payload with EDNS enabled. */ + KNOT_EDNS_MAX_UDP_PAYLOAD = 4096, + + /*! \brief Minimum size of EDNS OPT RR in wire format. */ + KNOT_EDNS_MIN_SIZE = 11, + /*! \brief Position of the Ext RCODE field in wire format of OPT RR. */ + KNOT_EDNS_EXT_RCODE_POS = 5, + /*! \brief EDNS OPTION header size. */ + KNOT_EDNS_OPTION_HDRLEN = 4, + + /*! \brief Maximal size of EDNS client subnet address in bytes (IPv6). */ + KNOT_EDNS_CLIENT_SUBNET_ADDRESS_MAXLEN = 16, + + /*! \brief Default EDNS alignment size for a query. */ + KNOT_EDNS_ALIGNMENT_QUERY_DEFALT = 128, + /*! \brief Default EDNS alignment size for a response. */ + KNOT_EDNS_ALIGNMENT_RESPONSE_DEFAULT = 468, + + /*! \brief Current EDNS cookie version. */ + KNOT_EDNS_COOKIE_VERSION = 1, + /*! \brief EDNS client cookie size. */ + KNOT_EDNS_COOKIE_CLNT_SIZE = 8, + /*! \brief EDNS minimum server cookie size. */ + KNOT_EDNS_COOKIE_SRVR_MIN_SIZE = 8, + /*! \brief EDNS maximum server cookie size. */ + KNOT_EDNS_COOKIE_SRVR_MAX_SIZE = 32, + + /*! \brief NSID option code. */ + KNOT_EDNS_OPTION_NSID = 3, + /*! \brief EDNS Client subnet option code. */ + KNOT_EDNS_OPTION_CLIENT_SUBNET = 8, + /*! \brief EDNS DNS Cookie option code. */ + KNOT_EDNS_OPTION_COOKIE = 10, + /*! \brief EDNS TCP Keepalive option code. */ + KNOT_EDNS_OPTION_TCP_KEEPALIVE = 11, + /*! \brief EDNS Padding option code. */ + KNOT_EDNS_OPTION_PADDING = 12, + /*! \brief EDNS Chain query option code. */ + KNOT_EDNS_OPTION_CHAIN = 13, + + /*! \brief Maximal currently relevant option code. */ + KNOT_EDNS_MAX_OPTION_CODE = 14, + + /*! \brief EDNS Extended error code. BACKPORTED! */ + KNOT_EDNS_OPTION_EDE = 15, +}; + +/*! + * \brief Extended error codes as in EDNS option #15. + * \warning Don't mix this up with EDNS extended errcode. + */ +enum { + KNOT_EDNS_EDE_OTHER = 0, + KNOT_EDNS_EDE_DNSKEY_ALG = 1, + KNOT_EDNS_EDE_DS_DIGEST = 2, + KNOT_EDNS_EDE_STALE = 3, + KNOT_EDNS_EDE_FORGED = 4, + KNOT_EDNS_EDE_INDETERMINATE = 5, + KNOT_EDNS_EDE_BOGUS = 6, + KNOT_EDNS_EDE_SIG_EXPIRED = 7, + KNOT_EDNS_EDE_SIG_NOTYET = 8, + KNOT_EDNS_EDE_DNSKEY_MISS = 9, + KNOT_EDNS_EDE_RRSIG_MISS = 10, + KNOT_EDNS_EDE_DNSKEY_BIT = 11, + KNOT_EDNS_EDE_NSEC_MISS = 12, + KNOT_EDNS_EDE_CACHED_ERR = 13, + KNOT_EDNS_EDE_NOT_READY = 14, + KNOT_EDNS_EDE_BLOCKED = 15, + KNOT_EDNS_EDE_CENSORED = 16, + KNOT_EDNS_EDE_FILTERED = 17, + KNOT_EDNS_EDE_PROHIBITED = 18, + KNOT_EDNS_EDE_STALE_NXD = 19, + KNOT_EDNS_EDE_NOTAUTH = 20, + KNOT_EDNS_EDE_NOTSUP = 21, + KNOT_EDNS_EDE_NREACH_AUTH = 22, + KNOT_EDNS_EDE_NETWORK = 23, + KNOT_EDNS_EDE_INV_DATA = 24, +}; + +/* Helpers for splitting extended RCODE. */ +#define KNOT_EDNS_RCODE_HI(rc) ((rc >> 4) & 0x00ff) +#define KNOT_EDNS_RCODE_LO(rc) (rc & 0x000f) + +/*! + * \brief Initialize OPT RR. + * + * \param opt_rr OPT RR to initialize. + * \param max_pld Max UDP payload. + * \param ext_rcode Extended RCODE. + * \param ver Version. + * \param mm Memory context. + * + * \return KNOT_EOK or an error + */ +int knot_edns_init(knot_rrset_t *opt_rr, uint16_t max_pld, + uint8_t ext_rcode, uint8_t ver, knot_mm_t *mm); + +/*! + * \brief Returns size of the OPT RR in wire format. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * + * \param opt_rr OPT RR to count the wire size of. + * + * \return Size of the OPT RR in bytes. + */ +static inline +size_t knot_edns_wire_size(knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + return KNOT_EDNS_MIN_SIZE + opt_rr->rrs.rdata->len; +} + +/*! + * \brief Returns the Max UDP payload value stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * + * \param opt_rr OPT RR to get the value from. + * + * \return Max UDP payload in bytes. + */ +static inline +uint16_t knot_edns_get_payload(const knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->rclass; +} + +/*! + * \brief Sets the Max UDP payload field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * + * \param opt_rr OPT RR to set the value to. + * \param payload UDP payload in bytes. + */ +static inline +void knot_edns_set_payload(knot_rrset_t *opt_rr, uint16_t payload) +{ + assert(opt_rr != NULL); + opt_rr->rclass = payload; +} + +/*! + * \brief Returns the Extended RCODE stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR to get the Extended RCODE from. + * + * \return Extended RCODE. + */ +uint8_t knot_edns_get_ext_rcode(const knot_rrset_t *opt_rr); + +/*! + * \brief Concatenates OPT RR Extended RCODE field and normal RCODE to get the + * whole Extended RCODE. + * + * Extended RCODE is created by using the Extended RCODE field from OPT RR as + * higher 8 bits and the RCODE from DNS Header as the lower 4 bits, resulting + * in a 12-bit unsigned integer. (See RFC 6891, Section 6.1.3). + * + * \param ext_rcode Extended RCODE field from OPT RR. + * \param rcode RCODE from DNS Header. + * + * \return 12-bit Extended RCODE. + */ +static inline +uint16_t knot_edns_whole_rcode(uint8_t ext_rcode, uint8_t rcode) +{ + uint16_t high = ext_rcode; + return (high << 4) | rcode; +} + +/*! + * \brief Sets the Extended RCODE field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR to set the Extended RCODE to. + * \param ext_rcode Extended RCODE to set. + */ +void knot_edns_set_ext_rcode(knot_rrset_t *opt_rr, uint8_t ext_rcode); + +/*! + * \brief Sets the Extended RCODE field in OPT RR wire. + * + * \param opt_rr Position of the OPT RR in packet. + * \param ext_rcode Higher 8 bits of Extended RCODE. + */ +static inline +void knot_edns_set_ext_rcode_wire(uint8_t *opt_rr, uint8_t ext_rcode) +{ + assert(opt_rr != NULL); + *(opt_rr + KNOT_EDNS_EXT_RCODE_POS) = ext_rcode; +} + +/*! + * \brief Returns the EDNS version stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR to get the EDNS version from. + * + * \return EDNS version. + */ +uint8_t knot_edns_get_version(const knot_rrset_t *opt_rr); + +/*! + * \brief Sets the EDNS version field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR to set the EDNS version to. + * \param version EDNS version to set. + */ +void knot_edns_set_version(knot_rrset_t *opt_rr, uint8_t version); + +/*! + * \brief Returns the state of the DO bit in the OPT RR flags. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * + * \param opt_rr OPT RR to get the DO bit from. + * + * \return true if the DO bit is set. + * \return false if the DO bit is not set. + */ +static inline +bool knot_edns_do(const knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->ttl & KNOT_EDNS_DO_MASK; +} + +/*! + * \brief Sets the DO bit in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * + * \param opt_rr OPT RR to set the DO bit in. + */ +static inline +void knot_edns_set_do(knot_rrset_t *opt_rr) +{ + assert(opt_rr != NULL); + opt_rr->ttl |= KNOT_EDNS_DO_MASK; +} + +/*! + * \brief Add EDNS option into the package with empty (zeroed) content. + * + * \param[in] opt_rr OPT RR structure to reserve the option in. + * \param[in] code Option code. + * \param[in] size Desired option size. + * \param[out] wire_ptr Pointer to reserved option data (can be NULL). + * \param[in] mm Memory context. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_reserve_option(knot_rrset_t *opt_rr, uint16_t code, + uint16_t size, uint8_t **wire_ptr, knot_mm_t *mm); + +/*! + * \brief Adds EDNS Option to the OPT RR. + * + * \note The function now supports adding empty OPTION (just having its code). + * + * \param opt_rr OPT RR structure to add the Option to. + * \param code Option code. + * \param size Option data length in bytes. + * \param data Option data. + * \param mm Memory context. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +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 Searches the OPT RR for option with the specified code. + * + * \param opt_rr OPT RR structure to search in. + * \param code Option code to search for. + * \param previous (Optional) Previously returned option to start searching from, + * to be able to return another option with the same code. + * + * \retval pointer to option if found + * \retval NULL otherwise. + */ +uint8_t *knot_edns_get_option(const knot_rrset_t *opt_rr, uint16_t code, + const uint8_t *previous); + +/*! + * \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. + * + * \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). + * + * \return EDNS option code + */ +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. + * + * \param opt EDNS option (including code, length and data portion). + * + * \return EDNS option length + */ +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. + * + * \warning No safety checks are performed on the supplied data. + * + * \param opt EDNS option (including code, length and data portion). + * + * \retval pointer to place where ENDS option data would reside + */ +static inline uint8_t *knot_edns_opt_get_data(uint8_t *opt) +{ + return opt + KNOT_EDNS_OPTION_HDRLEN; +} + +/*! + * \brief Computes additional Padding data length for required packet alignment. + * + * \param current_pkt_size Current packet size. + * \param current_opt_size Current OPT rrset size (OPT must be used). + * \param block_size Required packet block length (must be non-zero). + * + * \return Required padding length or -1 if padding not required. + */ +int knot_edns_alignment_size(size_t current_pkt_size, + size_t current_opt_size, + size_t block_size); + +/*! + * \brief EDNS Client Subnet content. + * + * \see draft-ietf-dnsop-edns-client-subnet + */ +typedef struct { + /*! \brief FAMILY */ + uint16_t family; + /*! \brief SOURCE PREFIX-LENGTH */ + uint8_t source_len; + /*! \brief SCOPE PREFIX-LENGTH */ + uint8_t scope_len; + /*! \brief ADDRESS */ + uint8_t address[KNOT_EDNS_CLIENT_SUBNET_ADDRESS_MAXLEN]; +} knot_edns_client_subnet_t; + +/*! + * \brief Get the wire size of the EDNS Client Subnet option. + * + * \param ecs EDNS Client Subnet data. + * + * \return Size of the EDNS option data. + */ +uint16_t knot_edns_client_subnet_size(const knot_edns_client_subnet_t *ecs); + +/*! + * \brief Write EDNS Client Subnet data from the ECS structure to wire. + * + * \param option EDNS option data buffer. + * \param option_len EDNS option data buffer size. + * \param ecs EDNS Client Subnet data. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_client_subnet_write(uint8_t *option, uint16_t option_len, + const knot_edns_client_subnet_t *ecs); + +/*! + * \brief Parse EDNS Client Subnet data from wire to the ECS structure. + * + * \param[out] ecs EDNS Client Subnet data. + * \param[in] option EDNS option data. + * \param[in] option_len EDNS option size. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_client_subnet_parse(knot_edns_client_subnet_t *ecs, + const uint8_t *option, uint16_t option_len); + +/*! + * \brief Set address to the ECS structure. + * + * \note It also resets the lengths. + * + * \param ecs ECS structure to set address into. + * \param addr Address to be set. + * + * \return Error code. KNOT_EOK if successful. + */ +int knot_edns_client_subnet_set_addr(knot_edns_client_subnet_t *ecs, + const struct sockaddr_storage *addr); + +/*! + * \brief Get address from the ECS structure. + * + * Only the family and raw address is set in the structure. The bits not + * covered by the prefix length are cleared. + * + * \param addr Address to be set. + * \param ecs ECS structure to retrieve address from. + */ +int knot_edns_client_subnet_get_addr(struct sockaddr_storage *addr, + const knot_edns_client_subnet_t *ecs); + +/*! + * \brief Get size of the EDNS Keepalive option wire size. + * + * \param[in] timeout EDNS TCP Keepalive timeout. + * + * \return Size of the EDNS option data. + */ +uint16_t knot_edns_keepalive_size(uint16_t timeout); + +/*! + * \brief Writes EDNS TCP Keepalive wire data. + * + * \param[out] option EDNS option data buffer. + * \param[in] option_len EDNS option data buffer size. + * \param[in] timeout EDNS TCP Keepalive timeout. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_keepalive_write(uint8_t *option, uint16_t option_len, uint16_t timeout); + +/*! + * \brief Parses EDNS TCP Keepalive wire data. + * + * \param[out] timeout EDNS TCP Keepalive timeout. + * \param[in] option EDNS option data. + * \param[in] option_len EDNS option size. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_keepalive_parse(uint16_t *timeout, const uint8_t *option, + uint16_t option_len); + +/*! + * \brief Get size of the EDNS Chain option wire size. + * + * \param[in] point EDNS Chain closest trusted point. + * + * \return Size of the EDNS option data or 0 if invalid input. + */ +uint16_t knot_edns_chain_size(const knot_dname_t *point); + +/*! + * \brief Writes EDNS Chain wire data. + * + * \param[out] option EDNS option data buffer. + * \param[in] option_len EDNS option data buffer size. + * \param[in] point EDNS Chain closest trusted point. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_chain_write(uint8_t *option, uint16_t option_len, + const knot_dname_t *point); + +/*! + * \brief Parses EDNS Chain wire data. + * + * \param[out] point EDNS Chain closest trusted point. + * \param[in] option EDNS option data. + * \param[in] option_len EDNS option size. + * \param[in] mm Memory context. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_chain_parse(knot_dname_t **point, const uint8_t *option, + uint16_t option_len, knot_mm_t *mm); + +/*! + * \brief DNS Cookie content. + */ +typedef struct { + uint8_t data[KNOT_EDNS_COOKIE_SRVR_MAX_SIZE]; /*!< Cookie data. */ + uint16_t len; /*!< Cookie length. */ +} knot_edns_cookie_t; + +/*! + * \brief Get size of the EDNS Cookie option wire size. + * + * \param[in] cc Client cookie. + * \param[in] sc Server cookie (can be NULL). + * + * \return Size of the EDNS option data or 0 if invalid input. + */ +uint16_t knot_edns_cookie_size(const knot_edns_cookie_t *cc, + const knot_edns_cookie_t *sc); + +/*! + * \brief Writes EDNS cookie wire data. + * + * \param[out] option EDNS option data buffer. + * \param[in] option_len EDNS option data buffer size. + * \param[in] cc EDNS client cookie. + * \param[in] sc EDNS server cookie (can be NULL). + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_cookie_write(uint8_t *option, uint16_t option_len, + const knot_edns_cookie_t *cc, + const knot_edns_cookie_t *sc); + +/*! + * \brief Parses EDNS Cookie wire data. + * + * \param[out] cc EDNS client cookie. + * \param[out] sc EDNS server cookie. + * \param[in] option EDNS option data. + * \param[in] option_len EDNS option size. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_edns_cookie_parse(knot_edns_cookie_t *cc, knot_edns_cookie_t *sc, + const uint8_t *option, uint16_t option_len); + +/*! + * \brief Returns description of option #15 extended error code. + * + * \warning Don't use with ext_errcode from knot_edns_get_ext_rcode(). + */ +const char *knot_edns_ede_strerr(uint16_t exterr_code); + +/*! @} */ diff --git a/src/libknot/rrtype/rdname.h b/src/libknot/rrtype/rdname.h new file mode 100644 index 0000000..e2b10c4 --- /dev/null +++ b/src/libknot/rrtype/rdname.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2020 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/descriptor.h" +#include "libknot/dname.h" +#include "libknot/rdata.h" + +static inline +const knot_dname_t *knot_cname_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +const knot_dname_t *knot_dname_target(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +const knot_dname_t *knot_ns_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +const knot_dname_t *knot_ptr_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +const knot_dname_t *knot_mx_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 2; +} + +static inline +const knot_dname_t *knot_srv_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 6; +} + +static inline +const knot_dname_t *knot_rdata_name(const knot_rdata_t *rdata, uint16_t type) +{ + assert(rdata); + switch (type) { + case KNOT_RRTYPE_NS: + return knot_ns_name(rdata); + case KNOT_RRTYPE_PTR: + return knot_ptr_name(rdata); + case KNOT_RRTYPE_MX: + return knot_mx_name(rdata); + case KNOT_RRTYPE_SRV: + return knot_srv_name(rdata); + case KNOT_RRTYPE_CNAME: + return knot_cname_name(rdata); + case KNOT_RRTYPE_DNAME: + return knot_dname_target(rdata); + } + + return NULL; +} + +/*! @} */ diff --git a/src/libknot/rrtype/rrsig.h b/src/libknot/rrtype/rrsig.h new file mode 100644 index 0000000..5a3643d --- /dev/null +++ b/src/libknot/rrtype/rrsig.h @@ -0,0 +1,100 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/dname.h" +#include "libknot/rdata.h" +#include "libknot/wire.h" + +static inline +uint16_t knot_rrsig_type_covered(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data); +} + +static inline +uint8_t knot_rrsig_alg(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 2); +} + +static inline +uint8_t knot_rrsig_labels(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 3); +} + +static inline +uint32_t knot_rrsig_original_ttl(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + 4); +} + +static inline +uint32_t knot_rrsig_sig_expiration(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + 8); +} + +static inline +uint32_t knot_rrsig_sig_inception(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + 12); +} + +static inline +uint16_t knot_rrsig_key_tag(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data + 16); +} + +static inline +const knot_dname_t *knot_rrsig_signer_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 18; +} + +static inline +uint16_t knot_rrsig_signature_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->len - 18 - knot_dname_size(knot_rrsig_signer_name(rdata)); +} + +static inline +const uint8_t *knot_rrsig_signature(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 18 + knot_dname_size(knot_rrsig_signer_name(rdata)); +} + +/*! @} */ diff --git a/src/libknot/rrtype/soa.h b/src/libknot/rrtype/soa.h new file mode 100644 index 0000000..9449f00 --- /dev/null +++ b/src/libknot/rrtype/soa.h @@ -0,0 +1,94 @@ +/* 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/>. + */ + +/*! + * \file + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include "libknot/dname.h" +#include "libknot/rdata.h" +#include "libknot/wire.h" + +static inline +const knot_dname_t *knot_soa_primary(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + +static inline +const knot_dname_t *knot_soa_mailbox(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + knot_dname_size(knot_soa_primary(rdata)); +} + +static inline +size_t knot_soa_names_len(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_dname_size(knot_soa_primary(rdata)) + + knot_dname_size(knot_soa_mailbox(rdata)); +} + +static inline +uint32_t knot_soa_serial(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + knot_soa_names_len(rdata)); +} + +static inline +void knot_soa_serial_set(knot_rdata_t *rdata, uint32_t serial) +{ + assert(rdata); + knot_wire_write_u32(rdata->data + knot_soa_names_len(rdata), serial); +} + +static inline +uint32_t knot_soa_refresh(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + knot_soa_names_len(rdata) + 4); +} + +static inline +uint32_t knot_soa_retry(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + knot_soa_names_len(rdata) + 8); +} + +static inline +uint32_t knot_soa_expire(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + knot_soa_names_len(rdata) + 12); +} + +static inline +uint32_t knot_soa_minimum(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data + knot_soa_names_len(rdata) + 16); +} + +/*! @} */ diff --git a/src/libknot/rrtype/tsig.c b/src/libknot/rrtype/tsig.c new file mode 100644 index 0000000..2925ac5 --- /dev/null +++ b/src/libknot/rrtype/tsig.c @@ -0,0 +1,404 @@ +/* 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 <inttypes.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <time.h> + +#include "libdnssec/tsig.h" +#include "libknot/attribute.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/consts.h" +#include "libknot/dname.h" +#include "libknot/errcode.h" +#include "libknot/rrset.h" +#include "libknot/wire.h" +#include "contrib/wire_ctx.h" + +/*! \brief TSIG field offsets. */ +typedef enum tsig_off_t { + TSIG_ALGNAME_O = 0, + TSIG_TSIGNED_O, + TSIG_FUDGE_O, + TSIG_MACLEN_O, + TSIG_MAC_O, + TSIG_ORIGID_O, + TSIG_ERROR_O, + TSIG_OLEN_O, + TSIG_OTHER_O +} tsig_off_t; + +/* Helpers for RDATA offset calculation. */ +#define TSIG_OFF_MACLEN (4 * sizeof(uint16_t)) +#define TSIG_FIXED_RDLEN (8 * sizeof(uint16_t)) +#define TSIG_OTHER_MAXLEN (3 * sizeof(uint16_t)) + +/*! + * \brief Seek offset of a TSIG RR field. + * + * \param rr TSIG RR. + * \param id Field index. + * \param nb Required number of bytes after the offset (for boundaries check). + * \return pointer to field on wire or NULL. + */ +static uint8_t* rdata_seek(const knot_rrset_t *rr, tsig_off_t id, size_t nb) +{ + const knot_rdata_t *rr_data = knot_rdataset_at(&rr->rrs, 0); + if (!rr_data || rr_data->len == 0) { + return NULL; + } + + wire_ctx_t wire = wire_ctx_init_const(rr_data->data, rr_data->len); + + /* TSIG RR names should be already sanitized on parse. */ + size_t alg_len = knot_dname_size(wire.wire); + + /* Not pretty, but fast. */ + switch (id) { + case TSIG_ALGNAME_O: break; + case TSIG_TSIGNED_O: + wire_ctx_skip(&wire, alg_len); break; + case TSIG_FUDGE_O: + wire_ctx_skip(&wire, alg_len + 3 * sizeof(uint16_t)); + break; + case TSIG_MACLEN_O: + wire_ctx_skip(&wire, alg_len + 4 * sizeof(uint16_t)); + break; + case TSIG_MAC_O: + wire_ctx_skip(&wire, alg_len + 5 * sizeof(uint16_t)); + break; + case TSIG_ORIGID_O: + wire_ctx_skip(&wire, alg_len + 4 * sizeof(uint16_t)); + wire_ctx_skip(&wire, wire_ctx_read_u16(&wire)); + break; + case TSIG_ERROR_O: + wire_ctx_skip(&wire, alg_len + 4 * sizeof(uint16_t)); + wire_ctx_skip(&wire, wire_ctx_read_u16(&wire)); + wire_ctx_skip(&wire, sizeof(uint16_t)); + break; + case TSIG_OLEN_O: + wire_ctx_skip(&wire, alg_len + 4 * sizeof(uint16_t)); + wire_ctx_skip(&wire, wire_ctx_read_u16(&wire)); + wire_ctx_skip(&wire, 2 * sizeof(uint16_t)); + break; + case TSIG_OTHER_O: + wire_ctx_skip(&wire, alg_len + 4 * sizeof(uint16_t)); + wire_ctx_skip(&wire, wire_ctx_read_u16(&wire)); + wire_ctx_skip(&wire, 3 * sizeof(uint16_t)); + break; + } + + if (wire.error != KNOT_EOK) { + return NULL; + } + + /* Check remaining bytes. */ + + if (wire_ctx_available(&wire) < nb){ + return NULL; + } + + return wire.position; +} + +static int rdata_set_tsig_error(knot_rrset_t *tsig, uint16_t tsig_error) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_ERROR_O, sizeof(uint16_t)); + if (!rd) { + return KNOT_ERROR; + } + + knot_wire_write_u16(rd, tsig_error); + return KNOT_EOK; +} + +_public_ +int knot_tsig_create_rdata(knot_rrset_t *rr, const knot_dname_t *alg, + uint16_t maclen, uint16_t tsig_err) +{ + if (rr == NULL || alg == NULL) { + return KNOT_EINVAL; + } + + size_t alg_len = knot_dname_size(alg); + size_t rdlen = alg_len + TSIG_FIXED_RDLEN + maclen; + if (tsig_err == KNOT_RCODE_BADTIME) { + rdlen += TSIG_OTHER_MAXLEN; + } + uint8_t rd[rdlen]; + memset(rd, 0, rdlen); + + /* Copy alg name. */ + knot_dname_to_wire(rd, alg, rdlen); + + /* Set MAC variable length in advance. */ + size_t offset = alg_len + TSIG_OFF_MACLEN; + knot_wire_write_u16(rd + offset, maclen); + + int ret = knot_rrset_add_rdata(rr, rd, rdlen, NULL); + if (ret != KNOT_EOK) { + return ret; + } + + /* Set error. */ + rdata_set_tsig_error(rr, tsig_err); + + return KNOT_EOK; +} + +_public_ +int knot_tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_TSIGNED_O, 3*sizeof(uint16_t)); + if (!rd) { + return KNOT_ERROR; + } + + knot_wire_write_u48(rd, time); + return KNOT_EOK; +} + +_public_ +int knot_tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_FUDGE_O, sizeof(uint16_t)); + if (!rd) { + return KNOT_ERROR; + } + + knot_wire_write_u16(rd, fudge); + return KNOT_EOK; +} + +_public_ +int knot_tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, const uint8_t *mac) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_MAC_O, length); + if (!rd) { + return KNOT_ERROR; + } + + /*! \note Cannot change length, as rdata is already preallocd. */ + + /* Copy the actual MAC. */ + memcpy(rd, mac, length); + return KNOT_EOK; +} + +_public_ +int knot_tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_ORIGID_O, sizeof(uint16_t)); + if (!rd) { + return KNOT_ERROR; + } + + /* Write the length - 2. */ + knot_wire_write_u16(rd, id); + return KNOT_EOK; +} + +_public_ +int knot_tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t len, + const uint8_t *other_data) +{ + if (len > TSIG_OTHER_MAXLEN) { + return KNOT_EINVAL; + } + + uint8_t *rd = rdata_seek(tsig, TSIG_OLEN_O, len + sizeof(uint16_t)); + if (!rd) { + return KNOT_ERROR; + } + + /* Write the length. */ + knot_wire_write_u16(rd, len); + + /* Copy the actual data. */ + memcpy(rd + sizeof(uint16_t), other_data, len); + return KNOT_EOK; +} + +_public_ +const knot_dname_t *knot_tsig_rdata_alg_name(const knot_rrset_t *tsig) +{ + return knot_rdataset_at(&tsig->rrs, 0)->data; +} + +_public_ +dnssec_tsig_algorithm_t knot_tsig_rdata_alg(const knot_rrset_t *tsig) +{ + /* Get the algorithm name. */ + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig); + if (!alg_name) { + return DNSSEC_TSIG_UNKNOWN; + } + + return dnssec_tsig_algorithm_from_dname(alg_name); +} + +_public_ +uint64_t knot_tsig_rdata_time_signed(const knot_rrset_t *tsig) +{ + /*! \todo How to return invalid value? */ + uint8_t *rd = rdata_seek(tsig, TSIG_TSIGNED_O, 3*sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u48(rd); +} + +_public_ +uint16_t knot_tsig_rdata_fudge(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_FUDGE_O, sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u16(rd); +} + +_public_ +const uint8_t *knot_tsig_rdata_mac(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_MAC_O, 0); + if (!rd) { + return NULL; + } + return rd; +} + +_public_ +size_t knot_tsig_rdata_mac_length(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_MACLEN_O, sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u16(rd); +} + +_public_ +uint16_t knot_tsig_rdata_orig_id(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_ORIGID_O, sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u16(rd); +} + +_public_ +uint16_t knot_tsig_rdata_error(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_ERROR_O, sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u16(rd); +} + +_public_ +const uint8_t *knot_tsig_rdata_other_data(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_OTHER_O, 0); + if (!rd) { + return NULL; + } + return rd; +} + +_public_ +uint16_t knot_tsig_rdata_other_data_length(const knot_rrset_t *tsig) +{ + uint8_t *rd = rdata_seek(tsig, TSIG_OLEN_O, sizeof(uint16_t)); + if (!rd) { + return 0; + } + return knot_wire_read_u16(rd); +} + +_public_ +size_t knot_tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig) +{ + if (tsig == NULL) { + return 0; + } + /* Key name, Algorithm name and Other data have variable lengths. */ + const knot_dname_t *key_name = tsig->owner; + if (!key_name) { + return 0; + } + + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig); + if (!alg_name) { + return 0; + } + + uint16_t other_data_length = knot_tsig_rdata_other_data_length(tsig); + + return knot_dname_size(key_name) + knot_dname_size(alg_name) + + other_data_length + KNOT_TSIG_VARIABLES_LENGTH; +} + +_public_ +size_t knot_tsig_rdata_tsig_timers_length(void) +{ + /*! \todo Cleanup */ + return KNOT_TSIG_TIMERS_LENGTH; +} + +_public_ +size_t knot_tsig_wire_size(const knot_tsig_key_t *key) +{ + if (key == NULL || key->name == NULL) { + return 0; + } + + return knot_dname_size(key->name) + TSIG_FIXED_RDLEN + + sizeof(uint16_t) + /* TYPE */ + sizeof(uint16_t) + /* CLASS */ + sizeof(uint32_t) + /* TTL */ + sizeof(uint16_t) + /* RDATA length. */ + knot_dname_size(dnssec_tsig_algorithm_to_dname(key->algorithm)) + + dnssec_tsig_algorithm_size(key->algorithm); /* MAC length. */ +} + +_public_ +size_t knot_tsig_wire_maxsize(const knot_tsig_key_t *key) +{ + size_t size = knot_tsig_wire_size(key); + if (size == 0) { + return 0; + } + + /* In case of BADTIME other data. */ + return size + TSIG_OTHER_MAXLEN; +} + +_public_ +bool knot_tsig_rdata_is_ok(const knot_rrset_t *tsig) +{ + /*! \todo Check size, needs to check variable-length fields. */ + return (tsig != NULL + && knot_rdataset_at(&tsig->rrs, 0) != NULL + && rdata_seek(tsig, TSIG_OTHER_O, 0) != NULL + && knot_tsig_rdata_alg_name(tsig) != NULL + && knot_tsig_rdata_time_signed(tsig) != 0); +} diff --git a/src/libknot/rrtype/tsig.h b/src/libknot/rrtype/tsig.h new file mode 100644 index 0000000..36c1f70 --- /dev/null +++ b/src/libknot/rrtype/tsig.h @@ -0,0 +1,122 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief TSIG manipulation. + * + * \addtogroup rrtype + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include "libdnssec/binary.h" +#include "libdnssec/tsig.h" +#include "libknot/consts.h" +#include "libknot/rrset.h" +#include "libknot/tsig.h" + +enum tsig_consts { + KNOT_TSIG_ITEM_COUNT = 7, + KNOT_TSIG_VARIABLES_LENGTH = sizeof(uint16_t) // class + + sizeof(uint32_t) // ttl + + 6 // time signed + + sizeof(uint16_t) // fudge + + sizeof(uint16_t) // error + + sizeof(uint16_t),// other data length + KNOT_TSIG_TIMERS_LENGTH = sizeof(uint16_t) //fugde + + 6 // time signed +}; + +/*! + * \brief Create TSIG RDATA. + * + * \param rr TSIG RR to contain created data. + * \param alg Algorithm name. + * \param maclen Algorithm MAC len (may be set to 0 for empty MAC). + * \param tsig_err TSIG error code. + * + * \retval KNOT_EINVAL + * \retval KNOT_EOK + */ +int knot_tsig_create_rdata(knot_rrset_t *rr, const knot_dname_t *alg, + uint16_t maclen, uint16_t tsig_err); + +int knot_tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time); + +int knot_tsig_rdata_store_current_time(knot_rrset_t *tsig); + +int knot_tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge); + +int knot_tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, const uint8_t *mac); + +int knot_tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id); + +int knot_tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t length, + const uint8_t *other_data); + +const knot_dname_t *knot_tsig_rdata_alg_name(const knot_rrset_t *tsig); + +dnssec_tsig_algorithm_t knot_tsig_rdata_alg(const knot_rrset_t *tsig); + +uint64_t knot_tsig_rdata_time_signed(const knot_rrset_t *tsig); + +uint16_t knot_tsig_rdata_fudge(const knot_rrset_t *tsig); + +const uint8_t *knot_tsig_rdata_mac(const knot_rrset_t *tsig); + +size_t knot_tsig_rdata_mac_length(const knot_rrset_t *tsig); + +uint16_t knot_tsig_rdata_orig_id(const knot_rrset_t *tsig); + +uint16_t knot_tsig_rdata_error(const knot_rrset_t *tsig); + +const uint8_t *knot_tsig_rdata_other_data(const knot_rrset_t *tsig); + +uint16_t knot_tsig_rdata_other_data_length(const knot_rrset_t *tsig); + +size_t knot_tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig); + +size_t knot_tsig_rdata_tsig_timers_length(void); + +/*! + * \brief Return standard TSIG RRSET wire size for given algorithm. + * + * \param key Signing key descriptor. + * + * \return RRSET wire size. + */ +size_t knot_tsig_wire_size(const knot_tsig_key_t *key); + +/*! + * \brief Return TSIG RRSET maximum wire size for given algorithm. + * + * This size is reached if error reply with BADTIME. + * + * \param key Signing key descriptor. + * + * \return RRSET wire size. + */ +size_t knot_tsig_wire_maxsize(const knot_tsig_key_t *key); + +/*! \todo Documentation. */ +bool knot_tsig_rdata_is_ok(const knot_rrset_t *tsig); + +/*! @} */ diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c new file mode 100644 index 0000000..b035a4a --- /dev/null +++ b/src/libknot/tsig-op.c @@ -0,0 +1,683 @@ +/* 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 <inttypes.h> +#include <time.h> +#include <stdint.h> + +#include "libdnssec/error.h" +#include "libdnssec/tsig.h" +#include "libknot/attribute.h" +#include "libknot/tsig-op.h" +#include "libknot/errcode.h" +#include "libknot/descriptor.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/packet/wire.h" +#include "libknot/consts.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/wire.h" +#include "contrib/string.h" + +const int KNOT_TSIG_MAX_DIGEST_SIZE = 64; // size of HMAC-SHA512 digest +const uint16_t KNOT_TSIG_FUDGE_DEFAULT = 300; // default Fudge value + +static int check_algorithm(const knot_rrset_t *tsig_rr) +{ + if (tsig_rr == NULL) { + return KNOT_EINVAL; + } + + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EMALF; + } + + dnssec_tsig_algorithm_t alg = dnssec_tsig_algorithm_from_dname(alg_name); + if (alg == DNSSEC_TSIG_UNKNOWN) { + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int check_key(const knot_rrset_t *tsig_rr, const knot_tsig_key_t *tsig_key) +{ + if (tsig_rr == NULL || tsig_key == NULL) { + return KNOT_EINVAL; + } + + const knot_dname_t *tsig_name = tsig_rr->owner; + if (!tsig_name) { + return KNOT_EMALF; + } + + if (!knot_dname_is_equal(tsig_name, tsig_key->name)) { + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int compute_digest(const uint8_t *wire, size_t wire_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key) +{ + if (!wire || !digest || !digest_len || !key) { + return KNOT_EINVAL; + } + + if (!key->name) { + return KNOT_EMALF; + } + + dnssec_tsig_ctx_t *ctx = NULL; + int result = dnssec_tsig_new(&ctx, key->algorithm, &key->secret); + if (result != DNSSEC_EOK) { + return KNOT_TSIG_EBADSIG; + } + + dnssec_binary_t cover = { .data = (uint8_t *)wire, .size = wire_len }; + dnssec_tsig_add(ctx, &cover); + + *digest_len = dnssec_tsig_size(ctx); + dnssec_tsig_write(ctx, digest); + dnssec_tsig_free(ctx); + + return KNOT_EOK; +} + +static int check_time_signed(const knot_rrset_t *tsig_rr, uint64_t prev_time_signed) +{ + if (!tsig_rr) { + return KNOT_EINVAL; + } + + /* Get the time signed and fudge values. */ + uint64_t time_signed = knot_tsig_rdata_time_signed(tsig_rr); + if (time_signed == 0) { + return KNOT_TSIG_EBADTIME; + } + uint16_t fudge = knot_tsig_rdata_fudge(tsig_rr); + if (fudge == 0) { + return KNOT_TSIG_EBADTIME; + } + + /* Get the current time. */ + time_t curr_time = time(NULL); + + /*!< \todo bleeding eyes. */ + double diff = difftime(curr_time, (time_t)time_signed); + + if (diff > fudge || diff < -fudge) { + return KNOT_TSIG_EBADTIME; + } + + diff = difftime((time_t)time_signed, prev_time_signed); + + if (diff < 0) { + return KNOT_TSIG_EBADTIME; + } + + return KNOT_EOK; +} + +static int write_tsig_variables(uint8_t *wire, const knot_rrset_t *tsig_rr) +{ + if (wire == NULL || tsig_rr == NULL) { + return KNOT_EINVAL; + } + + /* Copy TSIG variables - starting with key name. */ + const knot_dname_t *tsig_owner = tsig_rr->owner; + if (!tsig_owner) { + return KNOT_EINVAL; + } + + int offset = 0; + + offset += knot_dname_to_wire(wire + offset, tsig_owner, KNOT_DNAME_MAXLEN); + + /*!< \todo which order? */ + + /* Copy class. */ + knot_wire_write_u16(wire + offset, tsig_rr->rclass); + offset += sizeof(uint16_t); + + /* Copy TTL - always 0. */ + knot_wire_write_u32(wire + offset, tsig_rr->ttl); + offset += sizeof(uint32_t); + + /* Copy alg name. */ + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EINVAL; + } + + /* Te algorithm name must be in canonical form, i.e. in lowercase. */ + uint8_t *alg_name_wire = wire + offset; + offset += knot_dname_to_wire(alg_name_wire, alg_name, KNOT_DNAME_MAXLEN); + knot_dname_to_lower(alg_name_wire); + + /* Following data are written in network order. */ + /* Time signed. */ + knot_wire_write_u48(wire + offset, knot_tsig_rdata_time_signed(tsig_rr)); + offset += 6; + /* Fudge. */ + knot_wire_write_u16(wire + offset, knot_tsig_rdata_fudge(tsig_rr)); + offset += sizeof(uint16_t); + /* TSIG error. */ + knot_wire_write_u16(wire + offset, knot_tsig_rdata_error(tsig_rr)); + offset += sizeof(uint16_t); + /* Get other data length. */ + uint16_t other_data_length = knot_tsig_rdata_other_data_length(tsig_rr); + /* Get other data. */ + const uint8_t *other_data = knot_tsig_rdata_other_data(tsig_rr); + if (!other_data) { + return KNOT_EINVAL; + } + + /* + * We cannot write the whole other_data, as it contains its length in + * machine order. + */ + knot_wire_write_u16(wire + offset, other_data_length); + offset += sizeof(uint16_t); + + /* Skip the length. */ + memcpy(wire + offset, other_data, other_data_length); + + return KNOT_EOK; +} + +static int wire_write_timers(uint8_t *wire, const knot_rrset_t *tsig_rr) +{ + if (wire == NULL || tsig_rr == NULL) { + return KNOT_EINVAL; + } + + //write time signed + knot_wire_write_u48(wire, knot_tsig_rdata_time_signed(tsig_rr)); + //write fudge + knot_wire_write_u16(wire + 6, knot_tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +static int create_sign_wire(const uint8_t *msg, size_t msg_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_tsig_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + return KNOT_EINVAL; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + size_t wire_len = msg_len + request_mac_len + (request_mac_len > 0 ? 2 : 0) + + knot_tsig_rdata_tsig_variables_length(tmp_tsig); + uint8_t *wire = malloc(wire_len); + if (!wire) { + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + uint8_t *pos = wire; + + /* Copy the request MAC - should work even if NULL. */ + if (request_mac_len > 0) { + knot_wire_write_u16(pos, request_mac_len); + pos += 2; + memcpy(pos, request_mac, request_mac_len); + } + pos += request_mac_len; + /* Copy the original message. */ + memcpy(pos, msg, msg_len); + pos += msg_len; + /* Copy TSIG variables. */ + ret = write_tsig_variables(pos, tmp_tsig); + if (ret != KNOT_EOK) { + free(wire); + return ret; + } + + /* Compute digest. */ + ret = compute_digest(wire, wire_len, digest, digest_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + free(wire); + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +static int create_sign_wire_next(const uint8_t *msg, size_t msg_len, + const uint8_t *prev_mac, size_t prev_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_tsig_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + return KNOT_EINVAL; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + size_t wire_len = msg_len + prev_mac_len + knot_tsig_rdata_tsig_timers_length() + 2; + uint8_t *wire = malloc(wire_len); + if (!wire) { + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + /* Copy the request MAC - should work even if NULL. */ + knot_wire_write_u16(wire, prev_mac_len); + memcpy(wire + 2, prev_mac, prev_mac_len); + /* Copy the original message. */ + memcpy(wire + prev_mac_len + 2, msg, msg_len); + /* Copy TSIG variables. */ + + ret = wire_write_timers(wire + prev_mac_len + msg_len + 2, tmp_tsig); + if (ret != KNOT_EOK) { + free(wire); + return ret; + } + + /* Compute digest. */ + ret = compute_digest(wire, wire_len, digest, digest_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + free(wire); + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +_public_ +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint16_t tsig_rcode, + uint64_t request_time_signed) +{ + if (!msg || !msg_len || !key || digest == NULL || digest_len == NULL) { + return KNOT_EINVAL; + } + + knot_rrset_t *tmp_tsig = knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + uint16_t rdata_rcode = KNOT_RCODE_NOERROR; + if (tsig_rcode == KNOT_RCODE_BADTIME) { + rdata_rcode = tsig_rcode; + } + + const uint8_t *alg_name = dnssec_tsig_algorithm_to_dname(key->algorithm); + size_t alg_size = dnssec_tsig_algorithm_size(key->algorithm); + knot_tsig_create_rdata(tmp_tsig, alg_name, alg_size, rdata_rcode); + + /* Distinguish BADTIME response. */ + if (tsig_rcode == KNOT_RCODE_BADTIME) { + /* Set client's time signed into the time signed field. */ + knot_tsig_rdata_set_time_signed(tmp_tsig, request_time_signed); + + /* Store current time into Other data. */ + uint8_t time_signed[6]; + time_t now = time(NULL); + knot_wire_write_u48(time_signed, now); + + knot_tsig_rdata_set_other_data(tmp_tsig, 6, time_signed); + } else { + knot_tsig_rdata_set_time_signed(tmp_tsig, time(NULL)); + + /* Set other len. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, 0); + } + + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Set original ID */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + int ret = create_sign_wire(msg, *msg_len, /*msg_max_len,*/ + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, tmp_tsig, key); + if (ret != KNOT_EOK) { + knot_rrset_free(tmp_tsig, NULL); + return ret; + } + + /* Set the digest. */ + knot_tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + /* Write RRSet to wire */ + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + *digest_len = 0; + knot_rrset_free(tmp_tsig, NULL); + return ret; + } + + size_t tsig_wire_len = ret; + + knot_rrset_free(tmp_tsig, NULL); + + *msg_len += tsig_wire_len; + + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + /* everything went ok, save the digest to the output parameter */ + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +_public_ +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint8_t *to_sign, + size_t to_sign_len) +{ + if (!msg || !msg_len || !key || !digest || !digest_len) { + return KNOT_EINVAL; + } + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + knot_rrset_t *tmp_tsig = knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + const uint8_t *alg_name = dnssec_tsig_algorithm_to_dname(key->algorithm); + size_t alg_size = dnssec_tsig_algorithm_size(key->algorithm); + knot_tsig_create_rdata(tmp_tsig, alg_name, alg_size, 0); + knot_tsig_rdata_set_time_signed(tmp_tsig, time(NULL)); + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Create wire to be signed. */ + size_t wire_len = prev_digest_len + to_sign_len + KNOT_TSIG_TIMERS_LENGTH + 2; + uint8_t *wire = malloc(wire_len); + if (!wire) { + knot_rrset_free(tmp_tsig, NULL); + return KNOT_ENOMEM; + } + memset(wire, 0, wire_len); + + /* Write previous digest length. */ + knot_wire_write_u16(wire, prev_digest_len); + /* Write previous digest. */ + memcpy(wire + 2, prev_digest, prev_digest_len); + /* Write original message. */ + memcpy(wire + prev_digest_len + 2, to_sign, to_sign_len); + /* Write timers. */ + wire_write_timers(wire + prev_digest_len + to_sign_len + 2, tmp_tsig); + + int ret = compute_digest(wire, wire_len, digest_tmp, &digest_tmp_len, key); + free(wire); + if (ret != KNOT_EOK) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return ret; + } + + if (digest_tmp_len > *digest_len) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return KNOT_ESPACE; + } + + /* Set the MAC. */ + knot_tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + /* Set original id. */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set other data. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, NULL); + + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return ret; + } + + size_t tsig_wire_size = ret; + + knot_rrset_free(tmp_tsig, NULL); + + *msg_len += tsig_wire_size; + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int check_digest(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed, int use_times) +{ + if (!wire || !tsig_key) { + return KNOT_EINVAL; + } + + /* No TSIG record means verification failure. */ + if (tsig_rr == NULL) { + return KNOT_TSIG_EBADKEY; + } + + /* Check that libknot knows the algorithm. */ + int ret = check_algorithm(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + /* Check that key is valid, ie. the same as given in args. */ + ret = check_key(tsig_rr, tsig_key); + if (ret != KNOT_EOK) { + return ret; + } + + uint8_t *wire_to_sign = malloc(size); + if (!wire_to_sign) { + return KNOT_ENOMEM; + } + + memcpy(wire_to_sign, wire, size); + + // restore message ID to which the signature had been created with + knot_wire_set_id(wire_to_sign, knot_tsig_rdata_orig_id(tsig_rr)); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + assert(tsig_rr->rrs.count > 0); + + if (use_times) { + /* Wire is not a single packet, TSIG RRs must be stripped already. */ + ret = create_sign_wire_next(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } else { + ret = create_sign_wire(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } + + assert(tsig_rr->rrs.count > 0); + free(wire_to_sign); + + if (ret != KNOT_EOK) { + return ret; + } + + /* Compare MAC from TSIG RR RDATA with just computed digest. */ + + /*!< \todo move to function. */ + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + dnssec_tsig_algorithm_t alg = dnssec_tsig_algorithm_from_dname(alg_name); + + /*! \todo [TSIG] TRUNCATION */ + uint16_t mac_length = knot_tsig_rdata_mac_length(tsig_rr); + const uint8_t *tsig_mac = knot_tsig_rdata_mac(tsig_rr); + + if (mac_length != dnssec_tsig_algorithm_size(alg)) { + return KNOT_TSIG_EBADSIG; + } + + if (const_time_memcmp(tsig_mac, digest_tmp, mac_length) != 0) { + return KNOT_TSIG_EBADSIG; + } + + /* Check TSIG validity period, must be after the signature check! */ + ret = check_time_signed(tsig_rr, prev_time_signed); + if (ret != KNOT_EOK) { + return ret; + } + + return KNOT_EOK; +} + +_public_ +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_tsig_key_t *tsig_key) +{ + return check_digest(tsig_rr, wire, size, NULL, 0, tsig_key, 0, 0); +} + +_public_ +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed) +{ + return check_digest(tsig_rr, wire, size, request_mac, request_mac_len, + tsig_key, prev_time_signed, 0); +} + +_public_ +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed) +{ + return check_digest(tsig_rr, wire, size, prev_digest, + prev_digest_len, tsig_key, prev_time_signed, 1); +} + +_public_ +int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + uint16_t tsig_rcode, const knot_rrset_t *tsig_rr) +{ + /*! \todo Revise!! */ + + if (!msg || !msg_len || !tsig_rr) { + return KNOT_EINVAL; + } + + /*! \todo What key to use, when we do not sign? Does this even work? */ + knot_rrset_t *tmp_tsig = knot_rrset_new(tsig_rr->owner, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + assert(tsig_rcode != KNOT_RCODE_BADTIME); + knot_tsig_create_rdata(tmp_tsig, knot_tsig_rdata_alg_name(tsig_rr), 0, tsig_rcode); + knot_tsig_rdata_set_time_signed(tmp_tsig, knot_tsig_rdata_time_signed(tsig_rr)); + + /* Comparing to BIND it was found out that the Fudge should always be + * set to the server's value. + */ + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Set original ID */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set other len. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, 0); + + /* Append TSIG RR. */ + int ret = knot_tsig_append(msg, msg_len, msg_max_len, tmp_tsig); + + /* key_name already referenced in RRSet, no need to free separately. */ + knot_rrset_free(tmp_tsig, NULL); + + return ret; +} + +_public_ +int knot_tsig_append(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const knot_rrset_t *tsig_rr) +{ + /* Write RRSet to wire */ + int ret = knot_rrset_to_wire(tsig_rr, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + return ret; + } + + *msg_len += ret; + + knot_wire_set_arcount(msg, knot_wire_get_arcount(msg) + 1); + + return KNOT_EOK; +} diff --git a/src/libknot/tsig-op.h b/src/libknot/tsig-op.h new file mode 100644 index 0000000..03e744d --- /dev/null +++ b/src/libknot/tsig-op.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief TSIG signing and validating. + * + * \addtogroup knot-tsig + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include "libknot/rrtype/tsig.h" +#include "libknot/rrset.h" + +/*! + * \brief Generate TSIG signature of a message. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows sending the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param key TSIG used for signing. + * \param tsig_rcode RCODE of the TSIG. + * \param request_time_signed Clients time signed. + * + * \retval KNOT_EOK if everything went OK. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint16_t tsig_rcode, + uint64_t request_time_signed); + +/*! + * \brief Generate TSIG signature of a 2nd or later message in a TCP session. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows sending the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param key TSIG key for signing. + * \param to_sign Data being signed. + * \param to_sign_len Size of the data being signed. + * + * \retval KNOT_EOK if successful. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint8_t *to_sign, + size_t to_sign_len); + +/*! + * \brief Checks incoming request. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param tsig_key TSIG key. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_tsig_key_t *tsig_key); + +/*! + * \brief Checks incoming response. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * \param key TSIG key. + * \param prev_time_signed Time for TSIG period validity. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_tsig_key_t *key, + uint64_t prev_time_signed); + +/*! + * \brief Checks signature of 2nd or next packet in a TCP session. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * \param key TSIG key. + * \param prev_time_signed Time for TSIG period validity. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_tsig_key_t *key, + uint64_t prev_time_signed); + +/*! + * \todo Documentation! + */ +int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + uint16_t tsig_rcode, const knot_rrset_t *tsig_rr); + +/*! \brief Append TSIG RR to message. + * \todo Proper documentation. + */ +int knot_tsig_append(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const knot_rrset_t *tsig_rr); + +/*! \brief Return true if the TSIG RCODE allows signing the packet. + * \todo Proper documentation. + */ +static inline bool knot_tsig_can_sign(uint16_t tsig_rcode) { + return tsig_rcode == KNOT_RCODE_NOERROR || tsig_rcode == KNOT_RCODE_BADTIME; +} + +/*! @} */ diff --git a/src/libknot/tsig.c b/src/libknot/tsig.c new file mode 100644 index 0000000..479004b --- /dev/null +++ b/src/libknot/tsig.c @@ -0,0 +1,186 @@ +/* Copyright (C) 2019 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 <string.h> + +#include "contrib/getline.h" +#include "contrib/string.h" +#include "libdnssec/error.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/tsig.h" + +_public_ +void knot_tsig_key_deinit(knot_tsig_key_t *key) +{ + if (!key) { + return; + } + + knot_dname_free(key->name, NULL); + + memzero(key->secret.data, key->secret.size); + dnssec_binary_free(&key->secret); + + memzero(key, sizeof(*key)); +} + +_public_ +int knot_tsig_key_init(knot_tsig_key_t *key, const char *algorithm_name, + const char *name, const char *secret_b64) +{ + if (!name || !secret_b64 || !key) { + return KNOT_EINVAL; + } + + dnssec_tsig_algorithm_t algorithm = DNSSEC_TSIG_HMAC_SHA256; + if (algorithm_name != NULL) { + algorithm = dnssec_tsig_algorithm_from_name(algorithm_name); + if (algorithm == DNSSEC_TSIG_UNKNOWN) { + return KNOT_EMALF; + } + } + + knot_dname_t *dname = knot_dname_from_str_alloc(name); + if (!dname) { + return KNOT_ENOMEM; + } + knot_dname_to_lower(dname); + + dnssec_binary_t b64secret = { 0 }; + b64secret.data = (uint8_t *)secret_b64; + b64secret.size = strlen(secret_b64); + + dnssec_binary_t secret = { 0 }; + int result = dnssec_binary_from_base64(&b64secret, &secret); + if (result != KNOT_EOK) { + knot_dname_free(dname, NULL); + return result; + } + + key->name = dname; + key->algorithm = algorithm; + key->secret = secret; + + return KNOT_EOK; +} + +_public_ +int knot_tsig_key_init_str(knot_tsig_key_t *key, const char *params) +{ + if (!params) { + return KNOT_EINVAL; + } + + char *copy = strstrip(params); + if (!copy) { + return KNOT_ENOMEM; + } + + size_t copy_size = strlen(copy) + 1; + + // format [algorithm:]name:secret + + char *algorithm = NULL; + char *name = NULL; + char *secret = NULL; + + // find secret + + char *pos = strrchr(copy, ':'); + if (pos) { + *pos = '\0'; + secret = pos + 1; + } else { + memzero(copy, copy_size); + free(copy); + return KNOT_EMALF; + } + + // find name and optionally algorithm + + pos = strchr(copy, ':'); + if (pos) { + *pos = '\0'; + algorithm = copy; + name = pos + 1; + } else { + name = copy; + } + + int result = knot_tsig_key_init(key, algorithm, name, secret); + + memzero(copy, copy_size); + free(copy); + + return result; +} + +_public_ +int knot_tsig_key_init_file(knot_tsig_key_t *key, const char *filename) +{ + if (!filename) { + return KNOT_EINVAL; + } + + FILE *file = fopen(filename, "r"); + if (!file) { + return KNOT_EACCES; + } + + char *line = NULL; + size_t line_size = 0; + ssize_t read = knot_getline(&line, &line_size, file); + + fclose(file); + + if (read == -1) { + return KNOT_EMALF; + } + + int result = knot_tsig_key_init_str(key, line); + + memzero(line, line_size); + free(line); + + return result; +} + +_public_ +int knot_tsig_key_copy(knot_tsig_key_t *dst, const knot_tsig_key_t *src) +{ + if (!src || !dst) { + return KNOT_EINVAL; + } + + knot_tsig_key_t copy = { 0 }; + copy.algorithm = src->algorithm; + + copy.name = knot_dname_copy(src->name, NULL); + if (!copy.name) { + return KNOT_ENOMEM; + } + + if (dnssec_binary_dup(&src->secret, ©.secret) != DNSSEC_EOK) { + knot_tsig_key_deinit(©); + return KNOT_ENOMEM; + } + + *dst = copy; + + return KNOT_EOK; +} diff --git a/src/libknot/tsig.h b/src/libknot/tsig.h new file mode 100644 index 0000000..abbdde2 --- /dev/null +++ b/src/libknot/tsig.h @@ -0,0 +1,89 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief TSIG operations + * + * \addtogroup knot-tsig + * @{ + */ + +#pragma once + +#include "libknot/dname.h" +#include "libdnssec/tsig.h" + +/*! + * \brief TSIG key. + */ +typedef struct { + dnssec_tsig_algorithm_t algorithm; + knot_dname_t *name; + dnssec_binary_t secret; +} knot_tsig_key_t; + +/*! + * \brief Packet signing context. + */ +typedef struct { + knot_tsig_key_t tsig_key; + uint8_t *tsig_digest; + size_t tsig_digestlen; + uint64_t tsig_time_signed; + size_t pkt_count; +} knot_sign_context_t; + +/*! + * \brief Initialize a new TSIG key from individual key parameters. + * + * \param[out] key Key to be initialized. + * \param[in] algorithm Algorithm name. NULL for default (hmac-md5). + * \param[in] name Key name (domain name in presentation format). + * \param[in] secret_b64 Secret encoded using Base 64. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_tsig_key_init(knot_tsig_key_t *key, const char *algorithm, + const char *name, const char *secret_b64); + +/*! + * \brief Create a new TSIG key from a string encoding all parameters. + * + * \param[out] key Key to be initialized. + * \param[in] params Parameters in a form \a [algorithm:]name:base64_secret + */ +int knot_tsig_key_init_str(knot_tsig_key_t *key, const char *params); + +/*! + * \brief Create a new TSIG key by reading the parameters from a file. + * + * The file content is parsed by \a tsig_key_create_str. + */ +int knot_tsig_key_init_file(knot_tsig_key_t *key, const char *filename); + +/*! + * \brief Deinitialize TSIG key. + */ +void knot_tsig_key_deinit(knot_tsig_key_t *key); + +/*! + * \brief Duplicate a TSIG key. + */ +int knot_tsig_key_copy(knot_tsig_key_t *dst, const knot_tsig_key_t *src); + +/*! @} */ diff --git a/src/libknot/version.h b/src/libknot/version.h new file mode 100644 index 0000000..c9d7826 --- /dev/null +++ b/src/libknot/version.h @@ -0,0 +1,25 @@ +/* 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/>. + */ + +#pragma once + +#define KNOT_VERSION_MAJOR 3 +#define KNOT_VERSION_MINOR 0 +#define KNOT_VERSION_PATCH 0x05 + +#define KNOT_VERSION_HEX ((KNOT_VERSION_MAJOR << 16) | \ + (KNOT_VERSION_MINOR << 8) | \ + (KNOT_VERSION_PATCH)) diff --git a/src/libknot/version.h.in b/src/libknot/version.h.in new file mode 100644 index 0000000..37c02c6 --- /dev/null +++ b/src/libknot/version.h.in @@ -0,0 +1,25 @@ +/* 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/>. + */ + +#pragma once + +#define KNOT_VERSION_MAJOR @KNOT_VERSION_MAJOR@ +#define KNOT_VERSION_MINOR @KNOT_VERSION_MINOR@ +#define KNOT_VERSION_PATCH 0x0@KNOT_VERSION_PATCH@ + +#define KNOT_VERSION_HEX ((KNOT_VERSION_MAJOR << 16) | \ + (KNOT_VERSION_MINOR << 8) | \ + (KNOT_VERSION_PATCH)) diff --git a/src/libknot/wire.h b/src/libknot/wire.h new file mode 100644 index 0000000..2f264c7 --- /dev/null +++ b/src/libknot/wire.h @@ -0,0 +1,154 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Wire integer operations. + * + * \addtogroup wire + * @{ + */ + +#pragma once + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#include "libknot/endian.h" + +/*! + * \brief Reads 2 bytes from the wireformat data. + * + * \param pos Data to read the 2 bytes from. + * + * \return The 2 bytes read, in host byte order. + */ +inline static uint16_t knot_wire_read_u16(const uint8_t *pos) +{ + assert(pos); + uint16_t result; + memcpy(&result, pos, sizeof(result)); + return be16toh(result); +} + +/*! + * \brief Reads 4 bytes from the wireformat data. + * + * \param pos Data to read the 4 bytes from. + * + * \return The 4 bytes read, in host byte order. + */ +inline static uint32_t knot_wire_read_u32(const uint8_t *pos) +{ + assert(pos); + uint32_t result; + memcpy(&result, pos, sizeof(result)); + return be32toh(result); +} + +/*! + * \brief Reads 6 bytes from the wireformat data. + * + * \param pos Data to read the 6 bytes from. + * + * \return The 6 bytes read, in host byte order. + */ +inline static uint64_t knot_wire_read_u48(const uint8_t *pos) +{ + assert(pos); + uint64_t input = 0; + memcpy((uint8_t *)&input + 1, pos, 6); + return be64toh(input) >> 8; +} + +/*! + * \brief Read 8 bytes from the wireformat data. + * + * \param pos Data to read the 8 bytes from. + * + * \return The 8 bytes read, in host byte order. + */ +inline static uint64_t knot_wire_read_u64(const uint8_t *pos) +{ + assert(pos); + uint64_t result; + memcpy(&result, pos, sizeof(result)); + return be64toh(result); +} + +/*! + * \brief Writes 2 bytes in wireformat. + * + * The data are stored in network byte order (big endian). + * + * \param pos Position where to put the 2 bytes. + * \param data Data to put. + */ +inline static void knot_wire_write_u16(uint8_t *pos, uint16_t data) +{ + assert(pos); + uint16_t beval = htobe16(data); + memcpy(pos, &beval, sizeof(beval)); +} + +/*! + * \brief Writes 4 bytes in wireformat. + * + * The data are stored in network byte order (big endian). + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +inline static void knot_wire_write_u32(uint8_t *pos, uint32_t data) +{ + assert(pos); + uint32_t beval = htobe32(data); + memcpy(pos, &beval, sizeof(beval)); +} + +/*! + * \brief Writes 6 bytes in wireformat. + * + * The data are stored in network byte order (big endian). + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +inline static void knot_wire_write_u48(uint8_t *pos, uint64_t data) +{ + assert(pos); + uint64_t swapped = htobe64(data << 8); + memcpy(pos, (uint8_t *)&swapped + 1, 6); +} + +/*! + * \brief Writes 8 bytes in wireformat. + * + * The data are stored in network byte order (big endian). + * + * \param pos Position where to put the 8 bytes. + * \param data Data to put. + */ +inline static void knot_wire_write_u64(uint8_t *pos, uint64_t data) +{ + assert(pos); + uint64_t beval = htobe64(data); + memcpy(pos, &beval, sizeof(beval)); +} + +/*! @} */ diff --git a/src/libknot/xdp/Makefile.am b/src/libknot/xdp/Makefile.am new file mode 100644 index 0000000..e4d309c --- /dev/null +++ b/src/libknot/xdp/Makefile.am @@ -0,0 +1,15 @@ +# Useful commands: +# make filter +# ip link show $eth +# sudo ip link set dev $eth xdp off +# sudo ip link set dev $eth xdp obj ./bpf-kernel.o + +EXTRA_DIST = bpf-kernel-obj.c bpf-kernel.c + +.PHONY: filter + +filter: + rm -f bpf-kernel.ll bpf-kernel.o bpf-kernel-obj.c + clang -S -target bpf -Wall -O2 -emit-llvm -c -DNDEBUG -o bpf-kernel.ll -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c + llc -march=bpf -filetype=obj -o bpf-kernel.o bpf-kernel.ll + xxd -i bpf-kernel.o > bpf-kernel-obj.c diff --git a/src/libknot/xdp/Makefile.in b/src/libknot/xdp/Makefile.in new file mode 100644 index 0000000..5fcf7a3 --- /dev/null +++ b/src/libknot/xdp/Makefile.in @@ -0,0 +1,533 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Useful commands: +# make filter +# ip link show $eth +# sudo ip link set dev $eth xdp off +# sudo ip link set dev $eth xdp obj ./bpf-kernel.o +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/libknot/xdp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cc_clang.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/ax_compare_version.m4 \ + $(top_srcdir)/m4/code-coverage.m4 \ + $(top_srcdir)/m4/knot-lib-version.m4 \ + $(top_srcdir)/m4/knot-module.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/sanitizer.m4 $(top_srcdir)/m4/visibility.m4 \ + $(top_srcdir)/m4/knot-version.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/src/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_CLANG_VERSION = @CC_CLANG_VERSION@ +CFLAGS = @CFLAGS@ +CFLAG_VISIBILITY = @CFLAG_VISIBILITY@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DNSTAP_CFLAGS = @DNSTAP_CFLAGS@ +DNSTAP_LIBS = @DNSTAP_LIBS@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +HAVE_VISIBILITY = @HAVE_VISIBILITY@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KNOT_VERSION_MAJOR = @KNOT_VERSION_MAJOR@ +KNOT_VERSION_MINOR = @KNOT_VERSION_MINOR@ +KNOT_VERSION_PATCH = @KNOT_VERSION_PATCH@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAG_EXCLUDE_LIBS = @LDFLAG_EXCLUDE_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROTOC_C = @PROTOC_C@ +RANLIB = @RANLIB@ +RELEASE_DATE = @RELEASE_DATE@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +STRIP = @STRIP@ +VERSION = @VERSION@ +XDP_VISIBLE_HEADERS = @XDP_VISIBLE_HEADERS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +cap_ng_CFLAGS = @cap_ng_CFLAGS@ +cap_ng_LIBS = @cap_ng_LIBS@ +conf_mapsize = @conf_mapsize@ +config_dir = @config_dir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dlopen_LIBS = @dlopen_LIBS@ +docdir = @docdir@ +dvidir = @dvidir@ +embedded_libbpf_CFLAGS = @embedded_libbpf_CFLAGS@ +embedded_libbpf_LIBS = @embedded_libbpf_LIBS@ +exec_prefix = @exec_prefix@ +fuzzer_CFLAGS = @fuzzer_CFLAGS@ +fuzzer_LDFLAGS = @fuzzer_LDFLAGS@ +gnutls_CFLAGS = @gnutls_CFLAGS@ +gnutls_LIBS = @gnutls_LIBS@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libbpf_CFLAGS = @libbpf_CFLAGS@ +libbpf_LIBS = @libbpf_LIBS@ +libdir = @libdir@ +libdnssec_SONAME = @libdnssec_SONAME@ +libdnssec_SOVERSION = @libdnssec_SOVERSION@ +libdnssec_VERSION_INFO = @libdnssec_VERSION_INFO@ +libedit_CFLAGS = @libedit_CFLAGS@ +libedit_LIBS = @libedit_LIBS@ +libelf_CFLAGS = @libelf_CFLAGS@ +libelf_LIBS = @libelf_LIBS@ +libexecdir = @libexecdir@ +libfstrm_CFLAGS = @libfstrm_CFLAGS@ +libfstrm_LIBS = @libfstrm_LIBS@ +libidn2_CFLAGS = @libidn2_CFLAGS@ +libidn2_LIBS = @libidn2_LIBS@ +libidn_CFLAGS = @libidn_CFLAGS@ +libidn_LIBS = @libidn_LIBS@ +libknot_SONAME = @libknot_SONAME@ +libknot_SOVERSION = @libknot_SOVERSION@ +libknot_VERSION_INFO = @libknot_VERSION_INFO@ +libmaxminddb_CFLAGS = @libmaxminddb_CFLAGS@ +libmaxminddb_LIBS = @libmaxminddb_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ +libprotobuf_c_CFLAGS = @libprotobuf_c_CFLAGS@ +libprotobuf_c_LIBS = @libprotobuf_c_LIBS@ +liburcu_CFLAGS = @liburcu_CFLAGS@ +liburcu_LIBS = @liburcu_LIBS@ +liburcu_PKGCONFIG = @liburcu_PKGCONFIG@ +libzscanner_SONAME = @libzscanner_SONAME@ +libzscanner_SOVERSION = @libzscanner_SOVERSION@ +libzscanner_VERSION_INFO = @libzscanner_VERSION_INFO@ +lmdb_CFLAGS = @lmdb_CFLAGS@ +lmdb_LIBS = @lmdb_LIBS@ +localedir = @localedir@ +localstatedir = @localstatedir@ +malloc_LIBS = @malloc_LIBS@ +mandir = @mandir@ +math_LIBS = @math_LIBS@ +mkdir_p = @mkdir_p@ +module_dir = @module_dir@ +module_instdir = @module_instdir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pthread_LIBS = @pthread_LIBS@ +run_dir = @run_dir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +storage_dir = @storage_dir@ +sysconfdir = @sysconfdir@ +systemd_CFLAGS = @systemd_CFLAGS@ +systemd_LIBS = @systemd_LIBS@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = bpf-kernel-obj.c bpf-kernel.c +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/libknot/xdp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/libknot/xdp/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +.PHONY: filter + +filter: + rm -f bpf-kernel.ll bpf-kernel.o bpf-kernel-obj.c + clang -S -target bpf -Wall -O2 -emit-llvm -c -DNDEBUG -o bpf-kernel.ll -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c + llc -march=bpf -filetype=obj -o bpf-kernel.o bpf-kernel.ll + xxd -i bpf-kernel.o > bpf-kernel-obj.c + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libknot/xdp/bpf-consts.h b/src/libknot/xdp/bpf-consts.h new file mode 100644 index 0000000..d40b1fd --- /dev/null +++ b/src/libknot/xdp/bpf-consts.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2020 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/>. + */ + +#pragma once + +enum { + KNOT_XDP_LISTEN_PORT_MASK = 0xFFFF0000, /*!< Listen port option mask. */ + KNOT_XDP_LISTEN_PORT_ALL = 1 << 16, /*!< Listen on all ports. */ + KNOT_XDP_LISTEN_PORT_DROP = 1 << 17, /*!< Drop all incoming messages. */ +}; diff --git a/src/libknot/xdp/bpf-kernel-obj.c b/src/libknot/xdp/bpf-kernel-obj.c new file mode 100644 index 0000000..27f82f9 --- /dev/null +++ b/src/libknot/xdp/bpf-kernel-obj.c @@ -0,0 +1,144 @@ +unsigned char bpf_kernel_o[] = { + 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x07, 0x00, 0x01, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x61, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x06, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x2d, 0x26, 0x53, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x34, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x04, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x4f, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x04, 0x14, 0x00, + 0x86, 0xdd, 0x00, 0x00, 0x55, 0x04, 0x4c, 0x00, 0x08, 0x00, 0x00, 0x00, + 0xbf, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x04, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2d, 0x24, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x05, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x55, 0x05, 0x43, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xb7, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x35, 0x14, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x05, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x15, 0x05, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0xb7, 0x07, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x67, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x57, 0x04, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x46, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x04, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2d, 0x24, 0x35, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x05, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x55, 0x05, 0x31, 0x00, 0x60, 0x00, 0x00, 0x00, + 0xb7, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0x14, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x55, 0x05, 0x06, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2d, 0x23, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x07, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x71, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x55, 0x05, 0x25, 0x00, 0x11, 0x00, 0x00, 0x00, + 0xbf, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2d, 0x23, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x62, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xdc, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x6d, 0x23, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xfc, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x02, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0x18, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x15, 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x61, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x02, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x15, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x69, 0x62, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x07, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0xa2, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0xf1, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x90, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xc8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x6d, + 0x61, 0x70, 0x73, 0x00, 0x2e, 0x72, 0x65, 0x6c, 0x78, 0x64, 0x70, 0x5f, + 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x64, 0x70, + 0x00, 0x78, 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x71, 0x69, + 0x64, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78, 0x64, + 0x70, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, + 0x64, 0x70, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x62, 0x70, 0x66, 0x2d, + 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x2e, 0x63, 0x00, 0x2e, 0x73, 0x74, + 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x73, 0x79, 0x6d, 0x74, 0x61, 0x62, + 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x38, 0x00, 0x4c, 0x42, 0x42, 0x30, + 0x5f, 0x37, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x31, 0x33, 0x00, 0x4c, + 0x42, 0x42, 0x30, 0x5f, 0x32, 0x32, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, + 0x32, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int bpf_kernel_o_len = 1688; diff --git a/src/libknot/xdp/bpf-kernel-obj.h b/src/libknot/xdp/bpf-kernel-obj.h new file mode 100644 index 0000000..7c3d759 --- /dev/null +++ b/src/libknot/xdp/bpf-kernel-obj.h @@ -0,0 +1,2 @@ +extern unsigned char bpf_kernel_o[]; +extern unsigned int bpf_kernel_o_len; diff --git a/src/libknot/xdp/bpf-kernel.c b/src/libknot/xdp/bpf-kernel.c new file mode 100644 index 0000000..a19a609 --- /dev/null +++ b/src/libknot/xdp/bpf-kernel.c @@ -0,0 +1,159 @@ +/* Copyright (C) 2020 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 <linux/if_ether.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/udp.h> + +#include "bpf-consts.h" +#include "../../contrib/libbpf/include/uapi/linux/bpf.h" +#include "../../contrib/libbpf/bpf/bpf_endian.h" +#include "../../contrib/libbpf/bpf/bpf_helpers.h" + +/* Don't fragment flag. */ +#define IP_DF 0x4000 + +/* Assume netdev has no more than 128 queues. */ +#define QUEUE_MAX 128 + +/* A set entry here means that the corresponding queue_id + * has an active AF_XDP socket bound to it. */ +struct bpf_map_def SEC("maps") qidconf_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(int), + .value_size = sizeof(int), + .max_entries = QUEUE_MAX, +}; +struct bpf_map_def SEC("maps") xsks_map = { + .type = BPF_MAP_TYPE_XSKMAP, + .key_size = sizeof(int), + .value_size = sizeof(int), + .max_entries = QUEUE_MAX, +}; + +struct ipv6_frag_hdr { + unsigned char nexthdr; + unsigned char whatever[7]; +} __attribute__((packed)); + +SEC("xdp_redirect_udp") +int xdp_redirect_udp_func(struct xdp_md *ctx) +{ + const void *data = (void *)(long)ctx->data; + const void *data_end = (void *)(long)ctx->data_end; + + const struct ethhdr *eth = data; + const struct iphdr *ip4; + const struct ipv6hdr *ip6; + const struct udphdr *udp; + + __u8 ip_proto; + __u8 fragmented = 0; + + /* Parse Ethernet header. */ + if ((void *)eth + sizeof(*eth) > data_end) { + return XDP_DROP; + } + data += sizeof(*eth); + + /* Parse IPv4 or IPv6 header. */ + switch (eth->h_proto) { + case __constant_htons(ETH_P_IP): + ip4 = data; + if ((void *)ip4 + sizeof(*ip4) > data_end) { + return XDP_DROP; + } + if (ip4->version != 4) { + return XDP_DROP; + } + if (ip4->frag_off != 0 && + ip4->frag_off != __constant_htons(IP_DF)) { + fragmented = 1; + } + ip_proto = ip4->protocol; + udp = data + ip4->ihl * 4; + break; + case __constant_htons(ETH_P_IPV6): + ip6 = data; + if ((void *)ip6 + sizeof(*ip6) > data_end) { + return XDP_DROP; + } + if (ip6->version != 6) { + return XDP_DROP; + } + ip_proto = ip6->nexthdr; + data += sizeof(*ip6); + if (ip_proto == IPPROTO_FRAGMENT) { + fragmented = 1; + const struct ipv6_frag_hdr *frag = data; + if ((void *)frag + sizeof(*frag) > data_end) { + return XDP_DROP; + } + ip_proto = frag->nexthdr; + data += sizeof(*frag); + } + udp = data; + break; + default: + /* Also applies to VLAN. */ + return XDP_PASS; + } + + /* Treat UDP only. */ + if (ip_proto != IPPROTO_UDP) { + return XDP_PASS; + } + + /* Parse UDP header. */ + if ((void *)udp + sizeof(*udp) > data_end) { + return XDP_DROP; + } + + /* Check the UDP length. */ + if (data_end - (void *)udp < __bpf_ntohs(udp->len)) { + return XDP_DROP; + } + + /* Get the queue options. */ + int index = ctx->rx_queue_index; + int *qidconf = bpf_map_lookup_elem(&qidconf_map, &index); + if (!qidconf) { + return XDP_ABORTED; + } + + /* Treat specified destination ports only. */ + __u32 port_info = *qidconf; + switch (port_info & KNOT_XDP_LISTEN_PORT_MASK) { + case KNOT_XDP_LISTEN_PORT_DROP: + return XDP_DROP; + case KNOT_XDP_LISTEN_PORT_ALL: + break; + default: + if (udp->dest != port_info) { + return XDP_PASS; + } + } + + /* Drop fragmented UDP datagrams. */ + if (fragmented) { + return XDP_DROP; + } + + /* Forward the packet to user space. */ + return bpf_redirect_map(&xsks_map, index, 0); +} diff --git a/src/libknot/xdp/bpf-user.c b/src/libknot/xdp/bpf-user.c new file mode 100644 index 0000000..2201a5c --- /dev/null +++ b/src/libknot/xdp/bpf-user.c @@ -0,0 +1,287 @@ +/* Copyright (C) 2020 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 <bpf/bpf.h> +#include <linux/if_link.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libknot/endian.h" +#include "libknot/error.h" +#include "libknot/xdp/bpf-kernel-obj.h" +#include "libknot/xdp/bpf-user.h" +#include "libknot/xdp/eth.h" +#include "contrib/openbsd/strlcpy.h" + +#define NO_BPF_MAPS 2 + +static inline bool IS_ERR_OR_NULL(const void *ptr) +{ + return (ptr == NULL) || (unsigned long)ptr >= (unsigned long)-4095; +} + +static int prog_load(struct bpf_object **pobj, int *prog_fd) +{ + struct bpf_program *prog, *first_prog = NULL; + struct bpf_object *obj; + + obj = bpf_object__open_mem(bpf_kernel_o, bpf_kernel_o_len, NULL); + if (IS_ERR_OR_NULL(obj)) { + return KNOT_ENOENT; + } + + bpf_object__for_each_program(prog, obj) { + bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); + if (first_prog == NULL) { + first_prog = prog; + } + } + + if (first_prog == NULL) { + bpf_object__close(obj); + return KNOT_ENOENT; + } + + int ret = bpf_object__load(obj); + if (ret != 0) { + bpf_object__close(obj); + return KNOT_EINVAL; + } + + *pobj = obj; + *prog_fd = bpf_program__fd(first_prog); + + return KNOT_EOK; +} + +static int ensure_prog(struct kxsk_iface *iface, bool overwrite) +{ + if (bpf_kernel_o_len < 2) { + return KNOT_ENOTSUP; + } + + /* Use libbpf for extracting BPF byte-code from BPF-ELF object, and + * loading this into the kernel via bpf-syscall. */ + int prog_fd; + int ret = prog_load(&iface->prog_obj, &prog_fd); + if (ret != KNOT_EOK) { + return KNOT_EPROGRAM; + } + + ret = bpf_set_link_xdp_fd(iface->if_index, prog_fd, + overwrite ? 0 : XDP_FLAGS_UPDATE_IF_NOEXIST); + if (ret != 0) { + close(prog_fd); + } + if (ret == -EBUSY && !overwrite) { // We try accepting the present program. + uint32_t prog_id = 0; + ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0); + if (ret == 0 && prog_id != 0) { + ret = prog_fd = bpf_prog_get_fd_by_id(prog_id); + } + } + if (ret < 0) { + return KNOT_EFD; + } else { + return prog_fd; + } +} + +/*! + * /brief Get FDs for the two maps and assign them into xsk_info-> fields. + * + * Inspired by xsk_lookup_bpf_maps() from libbpf before qidconf_map elimination. + */ +static int get_bpf_maps(int prog_fd, struct kxsk_iface *iface) +{ + uint32_t *map_ids = calloc(NO_BPF_MAPS, sizeof(*map_ids)); + if (map_ids == NULL) { + return KNOT_ENOMEM; + } + + struct bpf_prog_info prog_info = { + .nr_map_ids = NO_BPF_MAPS, + .map_ids = (__u64)(unsigned long)map_ids, + }; + + uint32_t prog_len = sizeof(struct bpf_prog_info); + int ret = bpf_obj_get_info_by_fd(prog_fd, &prog_info, &prog_len); + if (ret != 0) { + free(map_ids); + return ret; + } + + for (int i = 0; i < NO_BPF_MAPS; ++i) { + int fd = bpf_map_get_fd_by_id(map_ids[i]); + if (fd < 0) { + continue; + } + + struct bpf_map_info map_info; + uint32_t map_len = sizeof(struct bpf_map_info); + ret = bpf_obj_get_info_by_fd(fd, &map_info, &map_len); + if (ret != 0) { + close(fd); + continue; + } + + if (strcmp(map_info.name, "qidconf_map") == 0) { + iface->qidconf_map_fd = fd; + continue; + } + + if (strcmp(map_info.name, "xsks_map") == 0) { + iface->xsks_map_fd = fd; + continue; + } + + close(fd); + } + + if (iface->qidconf_map_fd < 0 || iface->xsks_map_fd < 0) { + close(iface->qidconf_map_fd); + close(iface->xsks_map_fd); + iface->qidconf_map_fd = iface->xsks_map_fd = -1; + free(map_ids); + return KNOT_ENOENT; + } + + free(map_ids); + return KNOT_EOK; +} + +static void unget_bpf_maps(struct kxsk_iface *iface) +{ + close(iface->qidconf_map_fd); + close(iface->xsks_map_fd); + iface->qidconf_map_fd = iface->xsks_map_fd = -1; +} + +int kxsk_socket_start(const struct kxsk_iface *iface, uint32_t listen_port, + struct xsk_socket *xsk) +{ + if (iface == NULL || xsk == NULL) { + return KNOT_EINVAL; + } + + int fd = xsk_socket__fd(xsk); + int ret = bpf_map_update_elem(iface->xsks_map_fd, &iface->if_queue, &fd, 0); + if (ret != 0) { + return ret; + } + + int qid = (listen_port & KNOT_XDP_LISTEN_PORT_MASK) | htobe16(listen_port & 0xFFFF); + ret = bpf_map_update_elem(iface->qidconf_map_fd, &iface->if_queue, &qid, 0); + if (ret != 0) { + bpf_map_delete_elem(iface->xsks_map_fd, &iface->if_queue); + } + return ret; +} + +void kxsk_socket_stop(const struct kxsk_iface *iface) +{ + if (iface == NULL) { + return; + } + + int qid = 0; + (void)bpf_map_update_elem(iface->qidconf_map_fd, &iface->if_queue, &qid, 0); + bpf_map_delete_elem(iface->xsks_map_fd, &iface->if_queue); +} + +int kxsk_iface_new(const char *if_name, int if_queue, knot_xdp_load_bpf_t load_bpf, + struct kxsk_iface **out_iface) +{ + if (if_name == NULL || out_iface == NULL) { + return KNOT_EINVAL; + } + + struct kxsk_iface *iface = calloc(1, sizeof(*iface) + IFNAMSIZ); + if (iface == NULL) { + return KNOT_ENOMEM; + } + iface->if_name = (char *)(iface + 1); + strlcpy((char *)iface->if_name, if_name, IFNAMSIZ); + iface->if_index = if_nametoindex(if_name); + if (iface->if_index == 0) { + free(iface); + return KNOT_EINVAL; + } + iface->if_queue = if_queue; + iface->qidconf_map_fd = iface->xsks_map_fd = -1; + + int ret; + switch (load_bpf) { + case KNOT_XDP_LOAD_BPF_NEVER: + (void)0; + uint32_t prog_id = 0; + ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0); + if (ret == 0) { + if (prog_id == 0) { + ret = KNOT_EPROGRAM; + } else { + ret = bpf_prog_get_fd_by_id(prog_id); + } + } + break; + case KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD: + (void)bpf_set_link_xdp_fd(iface->if_index, -1, 0); + sleep(1); + // FALLTHROUGH + case KNOT_XDP_LOAD_BPF_ALWAYS: + ret = ensure_prog(iface, true); + break; + case KNOT_XDP_LOAD_BPF_MAYBE: + ret = ensure_prog(iface, false); + break; + default: + return KNOT_EINVAL; + } + + if (ret >= 0) { + ret = get_bpf_maps(ret, iface); + } + if (ret < 0) { + free(iface); + return ret; + } + + knot_xdp_mode_t mode = knot_eth_xdp_mode(iface->if_index); + if (mode == KNOT_XDP_MODE_NONE) { + free(iface); + return KNOT_ENOTSUP; + } + + *out_iface = iface; + return KNOT_EOK; +} + +void kxsk_iface_free(struct kxsk_iface *iface) +{ + if (iface == NULL) { + return; + } + + unget_bpf_maps(iface); + + if (iface->prog_obj != NULL) { + bpf_object__close(iface->prog_obj); + } + + free(iface); +} diff --git a/src/libknot/xdp/bpf-user.h b/src/libknot/xdp/bpf-user.h new file mode 100644 index 0000000..ce30582 --- /dev/null +++ b/src/libknot/xdp/bpf-user.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2020 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/>. + */ + +#pragma once + +#include <bpf/xsk.h> + +#include "libknot/xdp/xdp.h" + +struct kxsk_iface { + /*! Interface name. */ + const char *if_name; + /*! Interface name index (derived from ifname). */ + int if_index; + /*! Network card queue id. */ + int if_queue; + + /*! Configuration BPF map file descriptor. */ + int qidconf_map_fd; + /*! XSK BPF map file descriptor. */ + int xsks_map_fd; + + /*! BPF program object. */ + struct bpf_object *prog_obj; +}; + +struct kxsk_umem { + /*! Fill queue: passing memory frames to kernel - ready to receive. */ + struct xsk_ring_prod fq; + /*! Completion queue: passing memory frames from kernel - after send finishes. */ + struct xsk_ring_cons cq; + /*! Handle internal to libbpf. */ + struct xsk_umem *umem; + + /*! The memory frames. */ + struct umem_frame *frames; + /*! The number of free frames (for TX). */ + uint32_t tx_free_count; + /*! Stack of indices of the free frames (for TX). */ + uint16_t tx_free_indices[]; +}; + +struct knot_xdp_socket { + /*! Receive queue: passing arrived packets from kernel. */ + struct xsk_ring_cons rx; + /*! Transmit queue: passing packets to kernel for sending. */ + struct xsk_ring_prod tx; + /*! Information about memory frames for all the passed packets. */ + struct kxsk_umem *umem; + /*! Handle internal to libbpf. */ + struct xsk_socket *xsk; + + /*! Interface context. */ + const struct kxsk_iface *iface; + + /*! The kernel has to be woken up by a syscall indication. */ + bool kernel_needs_wakeup; +}; + +/*! + * \brief Set up BPF program and map for one XDP socket. + * + * \param if_name Name of the net iface (e.g. eth0). + * \param if_queue Network card queue id. + * \param load_bpf Insert BPF program into packet processing. + * \param out_iface Output: created interface context. + * + * \return KNOT_E* or -errno + */ +int kxsk_iface_new(const char *if_name, int if_queue, knot_xdp_load_bpf_t load_bpf, + struct kxsk_iface **out_iface); + +/*! + * \brief Unload BPF maps for a socket. + * + * \note This keeps the loaded BPF program. We don't care. + * + * \param iface Interface context to be freed. + */ +void kxsk_iface_free(struct kxsk_iface *iface); + +/*! + * \brief Activate this AF_XDP socket through the BPF maps. + * + * \param iface Interface context. + * \param listen_port Port to listen on, or KNOT_XDP_LISTEN_PORT_* flag. + * \param xsk Socket ctx. + * + * \return KNOT_E* or -errno + */ +int kxsk_socket_start(const struct kxsk_iface *iface, uint32_t listen_port, + struct xsk_socket *xsk); + +/*! + * \brief Deactivate this AF_XDP socket through the BPF maps. + * + * \param iface Interface context. + */ +void kxsk_socket_stop(const struct kxsk_iface *iface); diff --git a/src/libknot/xdp/eth.c b/src/libknot/xdp/eth.c new file mode 100644 index 0000000..b3614b6 --- /dev/null +++ b/src/libknot/xdp/eth.c @@ -0,0 +1,129 @@ +/* Copyright (C) 2020 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 <bpf/libbpf.h> +#include <errno.h> +#include <ifaddrs.h> +#include <linux/ethtool.h> +#include <linux/if.h> +#include <linux/if_link.h> +#include <linux/sockios.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "contrib/openbsd/strlcpy.h" +#include "contrib/sockaddr.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/xdp/eth.h" + +_public_ +int knot_eth_queues(const char *devname) +{ + if (devname == NULL) { + return KNOT_EINVAL; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return knot_map_errno(); + } + + struct ethtool_channels ch = { + .cmd = ETHTOOL_GCHANNELS + }; + struct ifreq ifr = { + .ifr_data = &ch + }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + int ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + if (errno == EOPNOTSUPP) { + ret = 1; + } else { + ret = knot_map_errno(); + } + } else { + if (ch.combined_count == 0) { + ret = 1; + } else { + ret = ch.combined_count; + } + } + + close(fd); + return ret; +} + +_public_ +int knot_eth_name_from_addr(const struct sockaddr_storage *addr, char *out, + size_t out_len) +{ + if (addr == NULL || out == NULL) { + return KNOT_EINVAL; + } + + struct ifaddrs *ifaces = NULL; + if (getifaddrs(&ifaces) != 0) { + return -errno; + } + + size_t matches = 0; + char *match_name = NULL; + + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + const struct sockaddr_storage *ifss = (struct sockaddr_storage *)ifa->ifa_addr; + if (ifss == NULL) { // Observed on interfaces without any address. + continue; + } + + if ((ifss->ss_family == addr->ss_family && sockaddr_is_any(addr)) || + sockaddr_cmp(ifss, addr, true) == 0) { + matches++; + match_name = ifa->ifa_name; + } + } + + if (matches == 1) { + size_t len = strlcpy(out, match_name, out_len); + freeifaddrs(ifaces); + return (len >= out_len) ? KNOT_ESPACE : KNOT_EOK; + } + + freeifaddrs(ifaces); + return matches == 0 ? KNOT_EADDRNOTAVAIL : KNOT_ELIMIT; +} + +_public_ +knot_xdp_mode_t knot_eth_xdp_mode(int if_index) +{ + struct xdp_link_info info; + int ret = bpf_get_link_xdp_info(if_index, &info, sizeof(info), 0); + if (ret != 0) { + return KNOT_XDP_MODE_NONE; + } + + switch (info.attach_mode) { + case XDP_ATTACHED_DRV: + case XDP_ATTACHED_HW: + return KNOT_XDP_MODE_FULL; + case XDP_ATTACHED_SKB: + return KNOT_XDP_MODE_EMUL; + default: + return KNOT_XDP_MODE_NONE; + } +} diff --git a/src/libknot/xdp/eth.h b/src/libknot/xdp/eth.h new file mode 100644 index 0000000..d743741 --- /dev/null +++ b/src/libknot/xdp/eth.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2020 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/>. + */ + +#pragma once + +#include <stddef.h> + +/*! + * \brief Get number of combined queues of a network interface. + * + * \param devname Name of the ethdev (e.g. eth1). + * + * \retval < 0 KNOT_E* if error. + * \retval 1 Default no of queues if the dev does not support. + * \return > 0 Number of queues. + */ +int knot_eth_queues(const char *devname); + +/*! + * \brief Get the corresponding network interface name for the address. + * + * \param addr Address of the inteface. + * \param out Output buffer for the interface name. + * \param out_len Size of the output buffer. + * + * \return KNOT_E* + */ +int knot_eth_name_from_addr(const struct sockaddr_storage *addr, char *out, + size_t out_len); + +typedef enum { + KNOT_XDP_MODE_NONE, /*!< XDP not available, BPF not loaded, or error. */ + KNOT_XDP_MODE_FULL, /*!< Full XDP support in driver or HW. */ + KNOT_XDP_MODE_EMUL, /*!< Emulated XDP support. */ +} knot_xdp_mode_t; + +/*! + * \brief Return the current XDP mode of a network inteface. + * + * \param if_index Index of the interface, output from if_nametoindex(). + * + * \return Current XDP mode. + */ +knot_xdp_mode_t knot_eth_xdp_mode(int if_index); diff --git a/src/libknot/xdp/xdp.c b/src/libknot/xdp/xdp.c new file mode 100644 index 0000000..29dfe61 --- /dev/null +++ b/src/libknot/xdp/xdp.c @@ -0,0 +1,717 @@ +/* Copyright (C) 2020 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 <errno.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/udp.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/xdp/bpf-user.h" +#include "libknot/xdp/xdp.h" +#include "contrib/macros.h" + +/* Don't fragment flag. */ +#define IP_DF 0x4000 + +#define FRAME_SIZE 2048 +#define UMEM_FRAME_COUNT_RX 4096 +#define UMEM_FRAME_COUNT_TX UMEM_FRAME_COUNT_RX // No reason to differ so far. +#define UMEM_RING_LEN_RX (UMEM_FRAME_COUNT_RX * 2) +#define UMEM_RING_LEN_TX (UMEM_FRAME_COUNT_TX * 2) +#define UMEM_FRAME_COUNT (UMEM_FRAME_COUNT_RX + UMEM_FRAME_COUNT_TX) + +/* With recent compilers we statically check #defines for settings that + * get refused by AF_XDP drivers (in current versions, at least). */ +#if (__STDC_VERSION__ >= 201112L) +#define IS_POWER_OF_2(n) (((n) & (n - 1)) == 0) +_Static_assert((FRAME_SIZE == 4096 || FRAME_SIZE == 2048) + && IS_POWER_OF_2(UMEM_FRAME_COUNT) + /* The following two inequalities aren't required by drivers, but they allow + * our implementation assume that the rings can never get filled. */ + && IS_POWER_OF_2(UMEM_RING_LEN_RX) && UMEM_RING_LEN_RX > UMEM_FRAME_COUNT_RX + && IS_POWER_OF_2(UMEM_RING_LEN_TX) && UMEM_RING_LEN_TX > UMEM_FRAME_COUNT_TX + && UMEM_FRAME_COUNT_TX <= (1 << 16) /* see tx_free_indices */ + , "Incorrect #define combination for AF_XDP."); +#endif + +/*! \brief The memory layout of IPv4 umem frame. */ +struct udpv4 { + union { + uint8_t bytes[1]; + struct { + struct ethhdr eth; // No VLAN support; CRC at the "end" of .data! + struct iphdr ipv4; + struct udphdr udp; + uint8_t data[]; + } __attribute__((packed)); + }; +}; + +/*! \brief The memory layout of IPv6 umem frame. */ +struct udpv6 { + union { + uint8_t bytes[1]; + struct { + struct ethhdr eth; // No VLAN support; CRC at the "end" of .data! + struct ipv6hdr ipv6; + struct udphdr udp; + uint8_t data[]; + } __attribute__((packed)); + }; +}; + +/*! \brief The memory layout of each umem frame. */ +struct umem_frame { + union { + uint8_t bytes[FRAME_SIZE]; + union { + struct udpv4 udpv4; + struct udpv6 udpv6; + }; + }; +}; + +_public_ +const size_t KNOT_XDP_PAYLOAD_OFFSET4 = offsetof(struct udpv4, data) + offsetof(struct umem_frame, udpv4); +_public_ +const size_t KNOT_XDP_PAYLOAD_OFFSET6 = offsetof(struct udpv6, data) + offsetof(struct umem_frame, udpv6); + +static int configure_xsk_umem(struct kxsk_umem **out_umem) +{ + /* Allocate memory and call driver to create the UMEM. */ + struct kxsk_umem *umem = calloc(1, + offsetof(struct kxsk_umem, tx_free_indices) + + sizeof(umem->tx_free_indices[0]) * UMEM_FRAME_COUNT_TX); + if (umem == NULL) { + return KNOT_ENOMEM; + } + + int ret = posix_memalign((void **)&umem->frames, getpagesize(), + FRAME_SIZE * UMEM_FRAME_COUNT); + if (ret != 0) { + free(umem); + return KNOT_ENOMEM; + } + + const struct xsk_umem_config config = { + .fill_size = UMEM_RING_LEN_RX, + .comp_size = UMEM_RING_LEN_TX, + .frame_size = FRAME_SIZE, + .frame_headroom = 0, + }; + + ret = xsk_umem__create(&umem->umem, umem->frames, FRAME_SIZE * UMEM_FRAME_COUNT, + &umem->fq, &umem->cq, &config); + if (ret != KNOT_EOK) { + free(umem->frames); + free(umem); + return ret; + } + *out_umem = umem; + + /* Designate the starting chunk of buffers for TX, and put them onto the stack. */ + umem->tx_free_count = UMEM_FRAME_COUNT_TX; + for (uint32_t i = 0; i < UMEM_FRAME_COUNT_TX; ++i) { + umem->tx_free_indices[i] = i; + } + + /* Designate the rest of buffers for RX, and pass them to the driver. */ + uint32_t idx = 0; + ret = xsk_ring_prod__reserve(&umem->fq, UMEM_FRAME_COUNT_RX, &idx); + if (ret != UMEM_FRAME_COUNT - UMEM_FRAME_COUNT_TX) { + assert(0); + return KNOT_ERROR; + } + assert(idx == 0); + for (uint32_t i = UMEM_FRAME_COUNT_TX; i < UMEM_FRAME_COUNT; ++i) { + *xsk_ring_prod__fill_addr(&umem->fq, idx++) = i * FRAME_SIZE; + } + xsk_ring_prod__submit(&umem->fq, UMEM_FRAME_COUNT_RX); + + return KNOT_EOK; +} + +static void deconfigure_xsk_umem(struct kxsk_umem *umem) +{ + (void)xsk_umem__delete(umem->umem); + free(umem->frames); + free(umem); +} + +static int configure_xsk_socket(struct kxsk_umem *umem, + const struct kxsk_iface *iface, + knot_xdp_socket_t **out_sock) +{ + knot_xdp_socket_t *xsk_info = calloc(1, sizeof(*xsk_info)); + if (xsk_info == NULL) { + return KNOT_ENOMEM; + } + xsk_info->iface = iface; + xsk_info->umem = umem; + + const struct xsk_socket_config sock_conf = { + .tx_size = UMEM_RING_LEN_TX, + .rx_size = UMEM_RING_LEN_RX, + .libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, + }; + + int ret = xsk_socket__create(&xsk_info->xsk, iface->if_name, + iface->if_queue, umem->umem, + &xsk_info->rx, &xsk_info->tx, &sock_conf); + if (ret != 0) { + free(xsk_info); + return ret; + } + + *out_sock = xsk_info; + return KNOT_EOK; +} + +_public_ +int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue, + uint32_t listen_port, knot_xdp_load_bpf_t load_bpf) +{ + if (socket == NULL || if_name == NULL) { + return KNOT_EINVAL; + } + + struct kxsk_iface *iface; + int ret = kxsk_iface_new(if_name, if_queue, load_bpf, &iface); + if (ret != KNOT_EOK) { + return ret; + } + + /* Initialize shared packet_buffer for umem usage. */ + struct kxsk_umem *umem = NULL; + ret = configure_xsk_umem(&umem); + if (ret != KNOT_EOK) { + kxsk_iface_free(iface); + return ret; + } + + ret = configure_xsk_socket(umem, iface, socket); + if (ret != KNOT_EOK) { + deconfigure_xsk_umem(umem); + kxsk_iface_free(iface); + return ret; + } + + ret = kxsk_socket_start(iface, listen_port, (*socket)->xsk); + if (ret != KNOT_EOK) { + xsk_socket__delete((*socket)->xsk); + deconfigure_xsk_umem(umem); + kxsk_iface_free(iface); + free(*socket); + *socket = NULL; + return ret; + } + + return ret; +} + +_public_ +void knot_xdp_deinit(knot_xdp_socket_t *socket) +{ + if (socket == NULL) { + return; + } + + kxsk_socket_stop(socket->iface); + xsk_socket__delete(socket->xsk); + deconfigure_xsk_umem(socket->umem); + + kxsk_iface_free((struct kxsk_iface *)/*const-cast*/socket->iface); + free(socket); +} + +_public_ +int knot_xdp_socket_fd(knot_xdp_socket_t *socket) +{ + if (socket == NULL) { + return 0; + } + + return xsk_socket__fd(socket->xsk); +} + +static void tx_free_relative(struct kxsk_umem *umem, uint64_t addr_relative) +{ + /* The address may not point to *start* of buffer, but `/` solves that. */ + uint64_t index = addr_relative / FRAME_SIZE; + assert(index < UMEM_FRAME_COUNT); + umem->tx_free_indices[umem->tx_free_count++] = index; +} + +_public_ +void knot_xdp_send_prepare(knot_xdp_socket_t *socket) +{ + if (socket == NULL) { + return; + } + + struct kxsk_umem *const umem = socket->umem; + struct xsk_ring_cons *const cq = &umem->cq; + + uint32_t idx = 0; + const uint32_t completed = xsk_ring_cons__peek(cq, UINT32_MAX, &idx); + if (completed == 0) { + return; + } + assert(umem->tx_free_count + completed <= UMEM_FRAME_COUNT_TX); + + for (uint32_t i = 0; i < completed; ++i) { + uint64_t addr_relative = *xsk_ring_cons__comp_addr(cq, idx++); + tx_free_relative(umem, addr_relative); + } + + xsk_ring_cons__release(cq, completed); +} + +static struct umem_frame *alloc_tx_frame(struct kxsk_umem *umem) +{ + if (unlikely(umem->tx_free_count == 0)) { + return NULL; + } + + uint32_t index = umem->tx_free_indices[--umem->tx_free_count]; + return umem->frames + index; +} + +_public_ +int knot_xdp_send_alloc(knot_xdp_socket_t *socket, bool ipv6, knot_xdp_msg_t *out, + const knot_xdp_msg_t *in_reply_to) +{ + if (socket == NULL || out == NULL) { + return KNOT_EINVAL; + } + + size_t ofs = ipv6 ? KNOT_XDP_PAYLOAD_OFFSET6 : KNOT_XDP_PAYLOAD_OFFSET4; + + struct umem_frame *uframe = alloc_tx_frame(socket->umem); + if (uframe == NULL) { + return KNOT_ENOMEM; + } + + memset(out, 0, sizeof(*out)); + + out->payload.iov_base = ipv6 ? uframe->udpv6.data : uframe->udpv4.data; + out->payload.iov_len = MIN(UINT16_MAX, FRAME_SIZE - ofs); + + const struct ethhdr *eth = (struct ethhdr *)uframe; + out->eth_from = (void *)ð->h_source; + out->eth_to = (void *)ð->h_dest; + + if (in_reply_to != NULL) { + memcpy(out->eth_from, in_reply_to->eth_to, ETH_ALEN); + memcpy(out->eth_to, in_reply_to->eth_from, ETH_ALEN); + + memcpy(&out->ip_from, &in_reply_to->ip_to, sizeof(out->ip_from)); + memcpy(&out->ip_to, &in_reply_to->ip_from, sizeof(out->ip_to)); + } + + return KNOT_EOK; +} + +static uint16_t from32to16(uint32_t sum) +{ + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + return sum; +} + +static uint16_t ipv4_checksum(const uint8_t *ipv4_hdr) +{ + const uint16_t *h = (const uint16_t *)ipv4_hdr; + uint32_t sum32 = 0; + for (int i = 0; i < 10; ++i) { + if (i != 5) { + sum32 += h[i]; + } + } + return ~from32to16(sum32); +} + +/* Checksum endianness implementation notes for ipv4_checksum() and udp_checksum_step(). + * + * The basis for checksum is addition on big-endian 16-bit words, with bit 16 carrying + * over to bit 0. That can be viewed as first byte carrying to the second and the + * second one carrying back to the first one, i.e. a symmetrical situation. + * Therefore the result is the same even when arithmetics is done on litte-endian (!) + */ + +static void udp_checksum_step(size_t *result, const void *_data, size_t _data_len) +{ + assert(!(_data_len & 1)); + const uint16_t *data = _data; + size_t len = _data_len / 2; + while (len-- > 0) { + *result += *data++; + } +} + +static void udp_checksum_finish(size_t *result) +{ + while (*result > 0xffff) { + *result = (*result & 0xffff) + (*result >> 16); + } + if (*result != 0xffff) { + *result = ~*result; + } +} + +static uint8_t *msg_uframe_ptr(knot_xdp_socket_t *socket, const knot_xdp_msg_t *msg, + /* Next parameters are just for debugging. */ + bool ipv6) +{ + uint8_t *uNULL = NULL; + uint8_t *uframe_p = uNULL + ((msg->payload.iov_base - NULL) & ~(FRAME_SIZE - 1)); + +#ifndef NDEBUG + intptr_t pd = (uint8_t *)msg->payload.iov_base - uframe_p + - (ipv6 ? KNOT_XDP_PAYLOAD_OFFSET6 : KNOT_XDP_PAYLOAD_OFFSET4); + /* This assertion might fire in some OK cases. For example, the second branch + * had to be added for cases with "emulated" AF_XDP support. */ + assert(pd == XDP_PACKET_HEADROOM || pd == 0); + + const uint8_t *umem_mem_start = socket->umem->frames->bytes; + const uint8_t *umem_mem_end = umem_mem_start + FRAME_SIZE * UMEM_FRAME_COUNT; + assert(umem_mem_start <= uframe_p && uframe_p < umem_mem_end); +#endif + return uframe_p; +} + +static void xsk_sendmsg_ipv4(knot_xdp_socket_t *socket, const knot_xdp_msg_t *msg, + uint32_t index) +{ + uint8_t *uframe_p = msg_uframe_ptr(socket, msg, false); + struct umem_frame *uframe = (struct umem_frame *)uframe_p; + struct udpv4 *h = &uframe->udpv4; + + const struct sockaddr_in *src_v4 = (const struct sockaddr_in *)&msg->ip_from; + const struct sockaddr_in *dst_v4 = (const struct sockaddr_in *)&msg->ip_to; + const uint16_t udp_len = sizeof(h->udp) + msg->payload.iov_len; + + h->eth.h_proto = __constant_htons(ETH_P_IP); + + h->ipv4.version = IPVERSION; + h->ipv4.ihl = 5; + h->ipv4.tos = 0; + h->ipv4.tot_len = htobe16(5 * 4 + udp_len); + h->ipv4.id = 0; + h->ipv4.frag_off = 0; + h->ipv4.ttl = IPDEFTTL; + h->ipv4.protocol = IPPROTO_UDP; + memcpy(&h->ipv4.saddr, &src_v4->sin_addr, sizeof(src_v4->sin_addr)); + memcpy(&h->ipv4.daddr, &dst_v4->sin_addr, sizeof(dst_v4->sin_addr)); + h->ipv4.check = ipv4_checksum(h->bytes + sizeof(struct ethhdr)); + + h->udp.len = htobe16(udp_len); + h->udp.source = src_v4->sin_port; + h->udp.dest = dst_v4->sin_port; + h->udp.check = 0; // Optional for IPv4 - not computed. + + *xsk_ring_prod__tx_desc(&socket->tx, index) = (struct xdp_desc){ + .addr = h->bytes - socket->umem->frames->bytes, + .len = KNOT_XDP_PAYLOAD_OFFSET4 + msg->payload.iov_len + }; +} + +static void xsk_sendmsg_ipv6(knot_xdp_socket_t *socket, const knot_xdp_msg_t *msg, + uint32_t index) +{ + uint8_t *uframe_p = msg_uframe_ptr(socket, msg, true); + struct umem_frame *uframe = (struct umem_frame *)uframe_p; + struct udpv6 *h = &uframe->udpv6; + + const struct sockaddr_in6 *src_v6 = (const struct sockaddr_in6 *)&msg->ip_from; + const struct sockaddr_in6 *dst_v6 = (const struct sockaddr_in6 *)&msg->ip_to; + const uint16_t udp_len = sizeof(h->udp) + msg->payload.iov_len; + + h->eth.h_proto = __constant_htons(ETH_P_IPV6); + + h->ipv6.version = 6; + h->ipv6.priority = 0; + memset(h->ipv6.flow_lbl, 0, sizeof(h->ipv6.flow_lbl)); + h->ipv6.payload_len = htobe16(udp_len); + h->ipv6.nexthdr = IPPROTO_UDP; + h->ipv6.hop_limit = IPDEFTTL; + memcpy(&h->ipv6.saddr, &src_v6->sin6_addr, sizeof(src_v6->sin6_addr)); + memcpy(&h->ipv6.daddr, &dst_v6->sin6_addr, sizeof(dst_v6->sin6_addr)); + + h->udp.len = htobe16(udp_len); + h->udp.source = src_v6->sin6_port; + h->udp.dest = dst_v6->sin6_port; + h->udp.check = 0; // Mandatory for IPv6 - computed afterwards. + + size_t chk = 0; + udp_checksum_step(&chk, &h->ipv6.saddr, sizeof(h->ipv6.saddr)); + udp_checksum_step(&chk, &h->ipv6.daddr, sizeof(h->ipv6.daddr)); + udp_checksum_step(&chk, &h->udp.len, sizeof(h->udp.len)); + __be16 version = htobe16(h->ipv6.nexthdr); + udp_checksum_step(&chk, &version, sizeof(version)); + udp_checksum_step(&chk, &h->udp, sizeof(h->udp)); + size_t padded_len = msg->payload.iov_len; + if (padded_len & 1) { + ((uint8_t *)msg->payload.iov_base)[padded_len++] = 0; + } + udp_checksum_step(&chk, msg->payload.iov_base, padded_len); + udp_checksum_finish(&chk); + h->udp.check = chk; + + *xsk_ring_prod__tx_desc(&socket->tx, index) = (struct xdp_desc){ + .addr = h->bytes - socket->umem->frames->bytes, + .len = KNOT_XDP_PAYLOAD_OFFSET6 + msg->payload.iov_len + }; +} + +_public_ +int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count, uint32_t *sent) +{ + if (socket == NULL || msgs == NULL || sent == NULL) { + return KNOT_EINVAL; + } + + /* Now we want to do something close to + * xsk_ring_prod__reserve(&socket->tx, count, *idx) + * but we don't know in advance if we utilize *whole* `count`, + * and the API doesn't allow "cancelling reservations". + * Therefore we handle `socket->tx.cached_prod` by hand; + * that's simplified by the fact that there is always free space. + */ + assert(UMEM_RING_LEN_TX > UMEM_FRAME_COUNT_TX); + uint32_t idx = socket->tx.cached_prod; + + for (uint32_t i = 0; i < count; ++i) { + const knot_xdp_msg_t *msg = &msgs[i]; + + if (msg->payload.iov_len && msg->ip_from.sin6_family == AF_INET) { + xsk_sendmsg_ipv4(socket, msg, idx++); + } else if (msg->payload.iov_len && msg->ip_from.sin6_family == AF_INET6) { + xsk_sendmsg_ipv6(socket, msg, idx++); + } else { + /* Some problem; we just ignore this message. */ + uint64_t addr_relative = (uint8_t *)msg->payload.iov_base + - socket->umem->frames->bytes; + tx_free_relative(socket->umem, addr_relative); + } + } + + *sent = idx - socket->tx.cached_prod; + assert(*sent <= count); + socket->tx.cached_prod = idx; + xsk_ring_prod__submit(&socket->tx, *sent); + socket->kernel_needs_wakeup = true; + + return KNOT_EOK; +} + +_public_ +int knot_xdp_send_finish(knot_xdp_socket_t *socket) +{ + if (socket == NULL) { + return KNOT_EINVAL; + } + + /* Trigger sending queued packets. */ + if (!socket->kernel_needs_wakeup) { + return KNOT_EOK; + } + + int ret = sendto(xsk_socket__fd(socket->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); + const bool is_ok = (ret >= 0); + // List of "safe" errors taken from + // https://github.com/torvalds/linux/blame/master/samples/bpf/xdpsock_user.c + const bool is_again = !is_ok && (errno == ENOBUFS || errno == EAGAIN + || errno == EBUSY || errno == ENETDOWN); + // Some of the !is_ok cases are a little unclear - what to do about the syscall, + // including how caller of _sendmsg_finish() should react. + if (is_ok || !is_again) { + socket->kernel_needs_wakeup = false; + } + if (is_again) { + return KNOT_EAGAIN; + } else if (is_ok) { + return KNOT_EOK; + } else { + return -errno; + } + /* This syscall might be avoided with a newer kernel feature (>= 5.4): + https://www.kernel.org/doc/html/latest/networking/af_xdp.html#xdp-use-need-wakeup-bind-flag + Unfortunately it's not easy to continue supporting older kernels + when using this feature on newer ones. + */ +} + +static void rx_desc(knot_xdp_socket_t *socket, const struct xdp_desc *desc, + knot_xdp_msg_t *msg) +{ + uint8_t *uframe_p = socket->umem->frames->bytes + desc->addr; + const struct ethhdr *eth = (struct ethhdr *)uframe_p; + const struct iphdr *ip4 = NULL; + const struct ipv6hdr *ip6 = NULL; + const struct udphdr *udp = NULL; + + switch (eth->h_proto) { + case __constant_htons(ETH_P_IP): + ip4 = (struct iphdr *)(uframe_p + sizeof(struct ethhdr)); + // Next conditions are ensured by the BPF filter. + assert(ip4->version == 4); + assert(ip4->frag_off == 0 || + ip4->frag_off == __constant_htons(IP_DF)); + assert(ip4->protocol == IPPROTO_UDP); + // IPv4 header checksum is not verified! + udp = (struct udphdr *)(uframe_p + sizeof(struct ethhdr) + + ip4->ihl * 4); + break; + case __constant_htons(ETH_P_IPV6): + ip6 = (struct ipv6hdr *)(uframe_p + sizeof(struct ethhdr)); + // Next conditions are ensured by the BPF filter. + assert(ip6->version == 6); + assert(ip6->nexthdr == IPPROTO_UDP); + udp = (struct udphdr *)(uframe_p + sizeof(struct ethhdr) + + sizeof(struct ipv6hdr)); + break; + default: + assert(0); + msg->payload.iov_len = 0; + return; + } + // UDP checksum is not verified! + + assert(eth && (!!ip4 != !!ip6) && udp); + + // Process the packet; ownership is passed on, beware of holding frames. + + msg->payload.iov_base = (uint8_t *)udp + sizeof(struct udphdr); + msg->payload.iov_len = be16toh(udp->len) - sizeof(struct udphdr); + + msg->eth_from = (void *)ð->h_source; + msg->eth_to = (void *)ð->h_dest; + + if (ip4 != NULL) { + struct sockaddr_in *src_v4 = (struct sockaddr_in *)&msg->ip_from; + struct sockaddr_in *dst_v4 = (struct sockaddr_in *)&msg->ip_to; + memcpy(&src_v4->sin_addr, &ip4->saddr, sizeof(src_v4->sin_addr)); + memcpy(&dst_v4->sin_addr, &ip4->daddr, sizeof(dst_v4->sin_addr)); + src_v4->sin_port = udp->source; + dst_v4->sin_port = udp->dest; + src_v4->sin_family = AF_INET; + dst_v4->sin_family = AF_INET; + } else { + assert(ip6); + struct sockaddr_in6 *src_v6 = (struct sockaddr_in6 *)&msg->ip_from; + struct sockaddr_in6 *dst_v6 = (struct sockaddr_in6 *)&msg->ip_to; + memcpy(&src_v6->sin6_addr, &ip6->saddr, sizeof(src_v6->sin6_addr)); + memcpy(&dst_v6->sin6_addr, &ip6->daddr, sizeof(dst_v6->sin6_addr)); + src_v6->sin6_port = udp->source; + dst_v6->sin6_port = udp->dest; + src_v6->sin6_family = AF_INET6; + dst_v6->sin6_family = AF_INET6; + // Flow label is ignored. + } +} + +_public_ +int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[], + uint32_t max_count, uint32_t *count) +{ + if (socket == NULL || msgs == NULL || count == NULL) { + return KNOT_EINVAL; + } + + uint32_t idx = 0; + const uint32_t available = xsk_ring_cons__peek(&socket->rx, max_count, &idx); + if (available == 0) { + *count = 0; + return KNOT_EOK; + } + assert(available <= max_count); + + for (uint32_t i = 0; i < available; ++i) { + rx_desc(socket, xsk_ring_cons__rx_desc(&socket->rx, idx++), &msgs[i]); + } + + xsk_ring_cons__release(&socket->rx, available); + *count = available; + + return KNOT_EOK; +} + +_public_ +void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count) +{ + if (socket == NULL || msgs == NULL) { + return; + } + + struct kxsk_umem *const umem = socket->umem; + struct xsk_ring_prod *const fq = &umem->fq; + + uint32_t idx = 0; + const uint32_t reserved = xsk_ring_prod__reserve(fq, count, &idx); + assert(reserved == count); + + for (uint32_t i = 0; i < reserved; ++i) { + uint8_t *uframe_p = msg_uframe_ptr(socket, &msgs[i], + msgs[i].ip_from.sin6_family == AF_INET6); + uint64_t offset = uframe_p - umem->frames->bytes; + *xsk_ring_prod__fill_addr(fq, idx++) = offset; + } + + xsk_ring_prod__submit(fq, reserved); +} + +_public_ +void knot_xdp_info(const knot_xdp_socket_t *socket, FILE *file) +{ + if (socket == NULL || file == NULL) { + return; + } + + // The number of busy frames + #define RING_BUSY(ring) \ + ((*(ring)->producer - *(ring)->consumer) & (ring)->mask) + + #define RING_PRINFO(name, ring) \ + fprintf(file, "Ring %s: size %4d, busy %4d (prod %4d, cons %4d)\n", \ + name, (unsigned)(ring)->size, \ + (unsigned)RING_BUSY((ring)), \ + (unsigned)*(ring)->producer, (unsigned)*(ring)->consumer) + + const int rx_busyf = RING_BUSY(&socket->umem->fq) + RING_BUSY(&socket->rx); + fprintf(file, "\nLOST RX frames: %4d", (int)(UMEM_FRAME_COUNT_RX - rx_busyf)); + + const int tx_busyf = RING_BUSY(&socket->umem->cq) + RING_BUSY(&socket->tx); + const int tx_freef = socket->umem->tx_free_count; + fprintf(file, "\nLOST TX frames: %4d\n", (int)(UMEM_FRAME_COUNT_TX - tx_busyf - tx_freef)); + + RING_PRINFO("FQ", &socket->umem->fq); + RING_PRINFO("RX", &socket->rx); + RING_PRINFO("TX", &socket->tx); + RING_PRINFO("CQ", &socket->umem->cq); + fprintf(file, "TX free frames: %4d\n", tx_freef); +} diff --git a/src/libknot/xdp/xdp.h b/src/libknot/xdp/xdp.h new file mode 100644 index 0000000..74a845a --- /dev/null +++ b/src/libknot/xdp/xdp.h @@ -0,0 +1,167 @@ +/* Copyright (C) 2020 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/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <netinet/in.h> + +#include "libknot/xdp/bpf-consts.h" + +#ifdef ENABLE_XDP +#define KNOT_XDP_AVAILABLE 1 +#endif + +/*! \brief A packet with src & dst MAC & IP addrs + UDP payload. */ +typedef struct knot_xdp_msg knot_xdp_msg_t; +struct knot_xdp_msg { + struct sockaddr_in6 ip_from; + struct sockaddr_in6 ip_to; + uint8_t *eth_from; + uint8_t *eth_to; + struct iovec payload; +}; + +/*! + * \brief Styles of loading BPF program. + * + * \note In *all* the cases loading can only succeed if at the end + * a compatible BPF program is loaded on the interface. + */ +typedef enum { + KNOT_XDP_LOAD_BPF_NEVER, /*!< Do not load; error out if not loaded already. */ + KNOT_XDP_LOAD_BPF_ALWAYS, /*!< Always load a program (overwrite it). */ + KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD, /*!< KNOT_XDP_LOAD_BPF_ALWAYS + unload previous. */ + KNOT_XDP_LOAD_BPF_MAYBE, /*!< Try with present program or load if none. */ + /* Implementation caveat: when re-using program in _MAYBE case, we get a message: + * libbpf: Kernel error message: XDP program already attached */ +} knot_xdp_load_bpf_t; + +/*! \brief Context structure for one XDP socket. */ +typedef struct knot_xdp_socket knot_xdp_socket_t; + +/*! \brief Offset of DNS payload inside ethernet frame (IPv4 and v6 variants). */ +extern const size_t KNOT_XDP_PAYLOAD_OFFSET4; +extern const size_t KNOT_XDP_PAYLOAD_OFFSET6; + +/*! + * \brief Initialize XDP socket. + * + * \param socket Socket ctx. + * \param if_name Name of the net iface (e.g. eth0). + * \param if_queue Network card queue to be used (normally 1 socket per each queue). + * \param listen_port Port to listen on, or KNOT_XDP_LISTEN_PORT_* flag. + * \param load_bpf Insert BPF program into packet processing. + * + * \return KNOT_E* or -errno + */ +int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue, + uint32_t listen_port, knot_xdp_load_bpf_t load_bpf); + +/*! + * \brief De-init XDP socket. + * + * \param socket XDP socket. + */ +void knot_xdp_deinit(knot_xdp_socket_t *socket); + +/*! + * \brief Return a file descriptor to be polled on for incomming packets. + * + * \param socket XDP socket. + * + * \return KNOT_E* + */ +int knot_xdp_socket_fd(knot_xdp_socket_t *socket); + +/*! + * \brief Collect completed TX buffers, so they can be used by knot_xdp_send_alloc(). + * + * \param socket XDP socket. + */ +void knot_xdp_send_prepare(knot_xdp_socket_t *socket); + +/*! + * \brief Allocate one buffer for an outgoing packet. + * + * \param socket XDP socket. + * \param ipv6 The packet will use IPv6 (IPv4 otherwise). + * \param out Out: the allocated packet buffer. + * \param in_reply_to Optional: fill in addresses from this query. + * + * \return KNOT_E* + */ +int knot_xdp_send_alloc(knot_xdp_socket_t *socket, bool ipv6, knot_xdp_msg_t *out, + const knot_xdp_msg_t *in_reply_to); + +/*! + * \brief Send multiple packets thru XDP. + * + * \note The packets all must have been allocated by knot_xdp_send_alloc()! + * \note Do not free the packets payloads afterwards. + * \note Packets with zero length will be skipped. + * + * \param socket XDP socket. + * \param msgs Packets to be sent. + * \param count Number of packets. + * \param sent Out: number of packet successfully sent. + * + * \return KNOT_E* + */ +int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count, uint32_t *sent); + +/*! + * \brief Syscall to kernel to wake up the network card driver after knot_xdp_send(). + * + * \param socket XDP socket. + * + * \return KNOT_E* or -errno + */ +int knot_xdp_send_finish(knot_xdp_socket_t *socket); + +/*! + * \brief Receive multiple packets thru XDP. + * + * \param socket XDP socket. + * \param msgs Out: buffers to be filled in with incomming packets. + * \param max_count Limit for number of packets received at once. + * \param count Out: real number of received packets. + * + * \return KNOT_E* + */ +int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[], + uint32_t max_count, uint32_t *count); + +/*! + * \brief Free buffers with received packets. + * + * \param socket XDP socket. + * \param msgs Buffers with received packets. + * \param count Number of received packets to free. + */ +void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count); + +/*! + * \brief Print some info about the XDP socket. + * + * \param socket XDP socket. + * \param file Output file. + */ +void knot_xdp_info(const knot_xdp_socket_t *socket, FILE *file); diff --git a/src/libknot/yparser/yparser.c b/src/libknot/yparser/yparser.c new file mode 100644 index 0000000..1171091 --- /dev/null +++ b/src/libknot/yparser/yparser.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2017 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 <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "libknot/yparser/yparser.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" + +extern int _yp_start_state; +extern int _yp_parse(yp_parser_t *parser); + +_public_ +void yp_init( + yp_parser_t *parser) +{ + if (parser == NULL) { + return; + } + + memset(parser, 0, sizeof(*parser)); + + parser->cs = _yp_start_state; + parser->file.descriptor = -1; + parser->line_count = 1; +} + +_public_ +void yp_deinit( + yp_parser_t *parser) +{ + if (parser == NULL) { + return; + } + + if (parser->file.descriptor != -1) { + munmap((void *)parser->input.start, + parser->input.end - parser->input.start); + close(parser->file.descriptor); + free(parser->file.name); + } +} + +_public_ +int yp_set_input_string( + yp_parser_t *parser, + const char *input, + size_t size) +{ + if (parser == NULL || input == NULL) { + return KNOT_EINVAL; + } + + // Reinitialize the parser. + yp_deinit(parser); + yp_init(parser); + + // Set the parser input limits. + parser->input.start = input; + parser->input.current = input; + parser->input.end = input + size; + parser->input.eof = false; + + return KNOT_EOK; +} + +_public_ +int yp_set_input_file( + yp_parser_t *parser, + const char *file_name) +{ + if (parser == NULL || file_name == NULL) { + return KNOT_EINVAL; + } + + // Reinitialize the parser. + yp_deinit(parser); + yp_init(parser); + + // Try to open the file. + parser->file.descriptor = open(file_name, O_RDONLY); + if (parser->file.descriptor == -1) { + return knot_map_errno(); + } + + // Check for regular file input. + struct stat file_stat; + if (fstat(parser->file.descriptor, &file_stat) == -1) { + close(parser->file.descriptor); + return knot_map_errno(); + } else if (!S_ISREG(file_stat.st_mode)) { + close(parser->file.descriptor); + return KNOT_EFILE; + } + + char *start = NULL; + + // Check for empty file (cannot mmap). + if (file_stat.st_size > 0) { + // Map the file to the memory. + start = mmap(0, file_stat.st_size, PROT_READ, MAP_SHARED, + parser->file.descriptor, 0); + if (start == MAP_FAILED) { + close(parser->file.descriptor); + return KNOT_ENOMEM; + } + + // Try to set the mapped memory advise to sequential. +#if defined(MADV_SEQUENTIAL) && !defined(__sun) + (void)madvise(start, file_stat.st_size, MADV_SEQUENTIAL); +#else +#ifdef POSIX_MADV_SEQUENTIAL + (void)posix_madvise(start, file_stat.st_size, POSIX_MADV_SEQUENTIAL); +#endif /* POSIX_MADV_SEQUENTIAL */ +#endif /* MADV_SEQUENTIAL && !__sun */ + + parser->input.eof = false; + } else { + parser->input.eof = true; + } + + parser->file.name = strdup(file_name); + + // Set the parser input limits. + parser->input.start = start; + parser->input.current = start; + parser->input.end = start + file_stat.st_size; + + return KNOT_EOK; +} + +_public_ +int yp_parse( + yp_parser_t *parser) +{ + if (parser == NULL) { + return KNOT_EINVAL; + } + + int ret = KNOT_EPARSEFAIL; + + // Run the parser until found new item, error or end of input. + do { + // Check for the end of the input. + if (parser->input.current == parser->input.end) { + if (parser->input.eof) { + // End of parsing. + return KNOT_EOF; + } else { + // Set the parser to final parsing. + parser->input.eof = true; + } + } + + // Parse the next item. + ret = _yp_parse(parser); + } while (ret == KNOT_EFEWDATA); + + return ret; +} diff --git a/src/libknot/yparser/yparser.h b/src/libknot/yparser/yparser.h new file mode 100644 index 0000000..8dab2dc --- /dev/null +++ b/src/libknot/yparser/yparser.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2015 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/>. + */ + +/*! + * \file + * + * \brief Simple parser (Yparser) of a YAML-inspired data format. + * + * \addtogroup yparser + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> + +/*! Maximal length of textual key value. */ +#define YP_MAX_TXT_KEY_LEN 128 +/*! Maximal length of textual data value. */ +#define YP_MAX_TXT_DATA_LEN 32768 + +/*! Parser events indicating type of lastly parsed item. */ +typedef enum { + YP_ENULL = 0, /*!< No valid data. */ + YP_EKEY0, /*!< First level item. */ + YP_EKEY1, /*!< Second level item. */ + YP_EID, /*!< Second level identifier. */ +} yp_event_t; + +/*! Context structure of yparser. */ +typedef struct { + /*! Current parser state (Ragel internals). */ + int cs; + /*! Indication if the current item was already processed. */ + bool processed; + /*! Current block indentation. */ + size_t indent; + /*! Last id dash position. */ + size_t id_pos; + + /*! Input parameters. */ + struct { + /*! Start of the block. */ + const char *start; + /*! Current parser position. */ + const char *current; + /*! End of the block. */ + const char *end; + /*! Indication for the final block parsing. */ + bool eof; + } input; + + /*! File input parameters. */ + struct { + /*! File name. */ + char *name; + /*! File descriptor. */ + int descriptor; + } file; + + /*! [out] Current line number (error location). */ + size_t line_count; + /*! [out] Current event. */ + yp_event_t event; + /*! [out] Parsed key (zero terminated string). */ + char key[YP_MAX_TXT_KEY_LEN]; + /*! [out] Key length. */ + size_t key_len; + /*! [out] Parsed data (zero terminated string). */ + char data[YP_MAX_TXT_DATA_LEN]; + /*! [out] Data length. */ + size_t data_len; +} yp_parser_t; + +/*! + * Initializes the parser. + * + * \param[in] parser Parser context. + */ +void yp_init( + yp_parser_t *parser +); + +/*! + * Deinitializes the parser. + * + * \param[in] parser Parser context. + */ +void yp_deinit( + yp_parser_t *parser +); + +/*! + * Sets the parser to parse given string. + * + * \param[in] parser Parser context. + * \param[in] input The string to parse. + * \param[in] size Length of the string. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_set_input_string( + yp_parser_t *parser, + const char *input, + size_t size +); + +/*! + * Sets the parser to parse given file. + * + * \param[in] parser Parser context. + * \param[in] file_name The filename to parse. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_set_input_file( + yp_parser_t *parser, + const char *file_name +); + +/*! + * Parses one item from the input. + * + * If the item has more values, this function returns for each value. The item + * can also have no value. + * + * \param[in] parser Parser context. + * + * \return Error code, KNOT_EOK if success, KNOT_EOF if end of data. + */ +int yp_parse( + yp_parser_t *parser +); + +/*! @} */ diff --git a/src/libknot/yparser/ypbody.c b/src/libknot/yparser/ypbody.c new file mode 100644 index 0000000..54ef509 --- /dev/null +++ b/src/libknot/yparser/ypbody.c @@ -0,0 +1,461 @@ + +/* Copyright (C) 2019 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 <string.h> + +#include "libknot/yparser/yparser.h" +#include "libknot/errcode.h" + + + + +// Include parser static data (Ragel internals). + +static const char _yparser_actions[] = { + 0, 1, 0, 1, 1, 1, 2, 1, + 3, 1, 4, 1, 6, 1, 8, 1, + 9, 1, 10, 1, 11, 1, 12, 1, + 15, 2, 1, 0, 2, 1, 2, 2, + 1, 15, 2, 2, 0, 2, 3, 4, + 2, 5, 4, 2, 6, 0, 2, 7, + 8, 2, 12, 13, 2, 14, 12, 3, + 1, 2, 0, 3, 1, 7, 8, 3, + 1, 12, 13, 3, 1, 14, 12, 3, + 2, 7, 8, 3, 2, 12, 13, 3, + 2, 14, 12, 4, 1, 2, 7, 8, + 4, 1, 2, 12, 13, 4, 1, 2, + 14, 12 +}; + +static const unsigned char _yparser_key_offsets[] = { + 0, 0, 15, 17, 29, 31, 34, 46, + 47, 58, 70, 72, 73, 83, 94, 98, + 100, 103, 107, 110, 113, 126, 136, 145, + 148, 151, 154, 158, 161, 164, 179, 194, + 209 +}; + +static const char _yparser_trans_keys[] = { + 10, 13, 32, 35, 42, 45, 46, 92, + 95, 48, 57, 65, 90, 97, 122, 10, + 13, 32, 58, 92, 95, 45, 46, 48, + 57, 65, 90, 97, 122, 32, 58, 10, + 13, 32, 32, 58, 92, 95, 45, 46, + 48, 57, 65, 90, 97, 122, 32, 32, + 42, 46, 92, 95, 48, 57, 65, 90, + 97, 122, 32, 58, 92, 95, 45, 46, + 48, 57, 65, 90, 97, 122, 32, 58, + 32, 32, 33, 34, 92, 36, 43, 45, + 90, 94, 126, 10, 13, 32, 33, 92, + 36, 43, 45, 90, 94, 126, 10, 13, + 32, 35, 10, 13, 34, 32, 126, 34, + 92, 32, 126, 10, 13, 32, 34, 32, + 126, 10, 13, 32, 34, 35, 91, 92, + 33, 43, 45, 90, 94, 126, 32, 33, + 34, 92, 36, 43, 45, 90, 94, 126, + 32, 33, 44, 92, 93, 36, 90, 94, + 126, 32, 44, 93, 10, 13, 32, 34, + 32, 126, 34, 92, 32, 126, 32, 44, + 93, 34, 32, 126, 10, 13, 32, 35, + 42, 45, 46, 92, 95, 48, 57, 65, + 90, 97, 122, 10, 13, 32, 35, 42, + 45, 46, 92, 95, 48, 57, 65, 90, + 97, 122, 10, 13, 32, 35, 42, 45, + 46, 92, 95, 48, 57, 65, 90, 97, + 122, 10, 13, 32, 35, 42, 45, 46, + 92, 95, 48, 57, 65, 90, 97, 122, + 0 +}; + +static const char _yparser_single_lengths[] = { + 0, 9, 2, 4, 2, 3, 4, 1, + 5, 4, 2, 1, 4, 5, 4, 2, + 1, 2, 3, 1, 7, 4, 5, 3, + 3, 1, 2, 3, 1, 9, 9, 9, + 9 +}; + +static const char _yparser_range_lengths[] = { + 0, 3, 0, 4, 0, 0, 4, 0, + 3, 4, 0, 0, 3, 3, 0, 0, + 1, 1, 0, 1, 3, 3, 2, 0, + 0, 1, 1, 0, 1, 3, 3, 3, + 3 +}; + +static const unsigned char _yparser_index_offsets[] = { + 0, 0, 13, 16, 25, 28, 32, 41, + 43, 52, 61, 64, 66, 74, 83, 88, + 91, 94, 98, 102, 105, 116, 124, 132, + 136, 140, 143, 147, 151, 154, 167, 180, + 193 +}; + +static const char _yparser_indicies[] = { + 1, 2, 3, 4, 5, 6, 5, 5, + 5, 5, 5, 5, 0, 1, 2, 4, + 7, 9, 8, 8, 8, 8, 8, 8, + 0, 10, 11, 0, 12, 13, 14, 0, + 15, 17, 16, 16, 16, 16, 16, 16, + 0, 18, 0, 18, 19, 19, 19, 19, + 19, 19, 19, 0, 20, 22, 21, 21, + 21, 21, 21, 21, 0, 23, 24, 0, + 25, 0, 25, 26, 27, 28, 26, 26, + 26, 0, 29, 30, 31, 32, 33, 32, + 32, 32, 0, 12, 13, 34, 35, 0, + 12, 13, 35, 36, 32, 0, 38, 39, + 37, 0, 29, 30, 31, 0, 40, 37, + 0, 12, 13, 14, 27, 35, 41, 28, + 26, 26, 26, 0, 41, 42, 43, 44, + 42, 42, 42, 0, 45, 46, 47, 48, + 49, 46, 46, 0, 50, 41, 51, 0, + 12, 13, 34, 0, 52, 46, 0, 54, + 55, 53, 0, 45, 47, 49, 0, 56, + 53, 0, 1, 2, 3, 4, 57, 6, + 57, 57, 57, 57, 57, 57, 0, 59, + 60, 61, 62, 63, 64, 63, 63, 63, + 63, 63, 63, 58, 65, 66, 67, 68, + 69, 70, 69, 69, 69, 69, 69, 69, + 0, 71, 72, 73, 74, 75, 76, 75, + 75, 75, 75, 75, 75, 58, 0 +}; + +static const char _yparser_trans_targs[] = { + 0, 30, 31, 1, 2, 3, 7, 4, + 3, 5, 4, 5, 32, 29, 20, 4, + 6, 5, 8, 9, 10, 9, 11, 10, + 11, 12, 13, 17, 16, 32, 29, 14, + 13, 16, 14, 15, 13, 17, 18, 19, + 17, 21, 22, 26, 25, 23, 22, 21, + 25, 24, 23, 24, 22, 26, 27, 28, + 26, 6, 0, 30, 31, 1, 2, 6, + 7, 30, 31, 1, 2, 6, 7, 30, + 31, 1, 2, 6, 7 +}; + +static const char _yparser_trans_actions[] = { + 23, 1, 0, 49, 0, 46, 52, 17, + 13, 17, 0, 0, 1, 0, 0, 15, + 13, 15, 21, 46, 19, 13, 19, 0, + 0, 0, 37, 7, 37, 43, 11, 11, + 9, 9, 0, 0, 40, 9, 0, 9, + 40, 0, 37, 7, 37, 11, 9, 11, + 9, 11, 0, 0, 40, 9, 0, 9, + 40, 46, 31, 55, 28, 88, 28, 83, + 93, 34, 5, 75, 5, 71, 79, 25, + 3, 63, 3, 59, 67 +}; + +static const char _yparser_eof_actions[] = { + 0, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 0, 28, 5, + 3 +}; + + + + + +int _yp_start_state = 29; + +int _yp_parse( + yp_parser_t *parser) +{ + // Parser input limits (Ragel internals). + const char *p, *pe, *eof; + + // Current item indent. + size_t indent = 0; + // Current id dash position. + size_t id_pos = 0; + // Indicates if the current parsing step contains an item. + bool found = false; + + if (!parser->input.eof) { // Restore parser input limits. + p = parser->input.current; + pe = parser->input.end; + eof = NULL; + } else { // Set the last artificial block with just one new line char. + p = "\n"; + pe = p + 1; + eof = pe; + } + + // Include parser body. + + { + int _klen; + unsigned int _trans; + const char *_acts; + unsigned int _nacts; + const char *_keys; + + if ( p == pe ) + goto _test_eof; + if ( parser->cs == 0 ) + goto _out; +_resume: + _keys = _yparser_trans_keys + _yparser_key_offsets[ parser->cs]; + _trans = _yparser_index_offsets[ parser->cs]; + + _klen = _yparser_single_lengths[ parser->cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_upper = _keys + _klen - 1; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + ((_upper-_lower) >> 1); + if ( (*p) < *_mid ) + _upper = _mid - 1; + else if ( (*p) > *_mid ) + _lower = _mid + 1; + else { + _trans += (unsigned int)(_mid - _keys); + goto _match; + } + } + _keys += _klen; + _trans += _klen; + } + + _klen = _yparser_range_lengths[ parser->cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_upper = _keys + (_klen<<1) - 2; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + (((_upper-_lower) >> 1) & ~1); + if ( (*p) < _mid[0] ) + _upper = _mid - 2; + else if ( (*p) > _mid[1] ) + _lower = _mid + 2; + else { + _trans += (unsigned int)((_mid - _keys)>>1); + goto _match; + } + } + _trans += _klen; + } + +_match: + _trans = _yparser_indicies[_trans]; + parser->cs = _yparser_trans_targs[_trans]; + + if ( _yparser_trans_actions[_trans] == 0 ) + goto _again; + + _acts = _yparser_actions + _yparser_trans_actions[_trans]; + _nacts = (unsigned int) *_acts++; + while ( _nacts-- > 0 ) + { + switch ( *_acts++ ) + { + case 0: + { + // Return if key without value. + if (parser->event != YP_ENULL && !parser->processed) { + parser->processed = true; + found = true; + {p++; goto _out; } + } + } + break; + case 1: + { + parser->line_count++; + parser->event = YP_ENULL; + parser->processed = false; + } + break; + case 2: + { + indent = 0; + id_pos = 0; + } + break; + case 3: + { + parser->data_len = 0; + } + break; + case 4: + { + if (parser->data_len >= sizeof(parser->data) - 1) { + return KNOT_ESPACE; + } + parser->data[parser->data_len++] = (*p); + } + break; + case 5: + { + parser->data_len--; + } + break; + case 6: + { + // Return if a value parsed. + parser->data[parser->data_len] = '\0'; + parser->processed = true; + found = true; + {p++; goto _out; } + } + break; + case 7: + { + if (indent > 0 && parser->indent > 0 && + indent != parser->indent) { + return KNOT_YP_EINVAL_INDENT; + } + parser->processed = false; + parser->key_len = 0; + parser->data_len = 0; + parser->event = YP_ENULL; + } + break; + case 8: + { + if (parser->key_len >= sizeof(parser->key) - 1) { + return KNOT_ESPACE; + } + parser->key[parser->key_len++] = (*p); + } + break; + case 9: + { + parser->key[parser->key_len] = '\0'; + parser->indent = 0; + parser->id_pos = 0; + parser->event = YP_EKEY0; + } + break; + case 10: + { + parser->key[parser->key_len] = '\0'; + parser->indent = indent; + parser->event = YP_EKEY1; + } + break; + case 11: + { + parser->key[parser->key_len] = '\0'; + parser->indent = indent; + parser->id_pos = id_pos; + parser->event = YP_EID; + } + break; + case 12: + { + indent++; + } + break; + case 13: + { + id_pos++; + } + break; + case 14: + { + if (id_pos > 0 && parser->id_pos > 0 && + id_pos != parser->id_pos) { + return KNOT_YP_EINVAL_INDENT; + } + parser->indent = 0; + } + break; + case 15: + { + switch ((*p)) { + case '\t': + return KNOT_YP_ECHAR_TAB; + default: + return KNOT_EPARSEFAIL; + } + } + break; + } + } + +_again: + if ( parser->cs == 0 ) + goto _out; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + if ( p == eof ) + { + const char *__acts = _yparser_actions + _yparser_eof_actions[ parser->cs]; + unsigned int __nacts = (unsigned int) *__acts++; + while ( __nacts-- > 0 ) { + switch ( *__acts++ ) { + case 1: + { + parser->line_count++; + parser->event = YP_ENULL; + parser->processed = false; + } + break; + case 2: + { + indent = 0; + id_pos = 0; + } + break; + case 15: + { + switch ((*p)) { + case '\t': + return KNOT_YP_ECHAR_TAB; + default: + return KNOT_EPARSEFAIL; + } + } + break; + } + } + } + + _out: {} + } + + + // Store the current parser position. + if (!parser->input.eof) { + parser->input.current = p; + } else { + parser->input.current = parser->input.end; + } + + // Check for general parser error. + if (parser->cs == 0) { + return KNOT_EPARSEFAIL; + } + + // Check if parsed an item. + if (found) { + return KNOT_EOK; + } else { + return KNOT_EFEWDATA; + } +} diff --git a/src/libknot/yparser/ypformat.c b/src/libknot/yparser/ypformat.c new file mode 100644 index 0000000..3284924 --- /dev/null +++ b/src/libknot/yparser/ypformat.c @@ -0,0 +1,121 @@ +/* Copyright (C) 2017 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 <stdio.h> + +#include "libknot/yparser/yptrafo.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" + +static int format_item( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style, + const char *prefix, + bool first_value, + bool last_value) +{ + if (item == NULL || out == NULL || prefix == NULL) { + return KNOT_EINVAL; + } + + // Format key part. + int ret = snprintf(out, out_len, "%s%s%s%s", + first_value ? prefix : "", + first_value ? item->name + 1 : "", + first_value ? ":" : "", + item->type == YP_TGRP ? + "\n" : (first_value && !last_value ? " [ " : " ")); + if (ret < 0 || ret >= out_len) { + return KNOT_ESPACE; + } + out += ret; + out_len -= ret; + + // Finish if group. + if (item->type == YP_TGRP) { + return KNOT_EOK; + } + + // Format data part. + size_t aux_len = out_len; + ret = yp_item_to_txt(item, data, data_len, out, &aux_len, style); + if (ret != KNOT_EOK) { + return ret; + } + out += aux_len; + out_len -= aux_len; + + // Format data end. + ret = snprintf(out, out_len, "%s%s", + last_value && !first_value ? " ]" : "", + last_value ? "\n" : ","); + if (ret < 0 || ret >= out_len) { + return KNOT_ESPACE; + } + + return KNOT_EOK; +} + +_public_ +int yp_format_key0( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style, + bool first_value, + bool last_value) +{ + return format_item(item, data, data_len, out, out_len, style, "", + first_value, last_value); +} + +_public_ +int yp_format_id( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style) +{ + if (data == NULL) { + return KNOT_EINVAL; + } + + return format_item(item, data, data_len, out, out_len, style, " - ", + true, true); +} + +_public_ +int yp_format_key1( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style, + bool first_value, + bool last_value) +{ + return format_item(item, data, data_len, out, out_len, style, " ", + first_value, last_value); +} diff --git a/src/libknot/yparser/ypformat.h b/src/libknot/yparser/ypformat.h new file mode 100644 index 0000000..c1d21bd --- /dev/null +++ b/src/libknot/yparser/ypformat.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief Tools for Yparser format creation. + * + * \addtogroup yparser + * @{ + */ + +#pragma once + +#include "libknot/yparser/ypschema.h" + +/*! + * Formats key0 item. + * + * \param[in] item Schema item to format. + * \param[in] data Data to format. + * \param[in] data_len Data length. + * \param[out] out Output buffer. + * \param[in, out] out_len Output buffer length, output length. + * \param[in] style Value style. + * \param[in] first_value First value indication (multivalued support). + * \param[in] last_value Last value indication (multivalued support). + * + * \return Error code, KNOT_EOK if success. + */ +int yp_format_key0( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style, + bool first_value, + bool last_value +); + +/*! + * Formats identifier item. + * + * \param[in] item Schema item to format. + * \param[in] data Data to format. + * \param[in] data_len Data length. + * \param[out] out Output buffer. + * \param[in, out] out_len Output buffer length, output length. + * \param[in] style Value style. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_format_id( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style +); + +/*! + * Formats key1 item. + * + * \param[in] item Schema item to format. + * \param[in] data Data to format. + * \param[in] data_len Data length. + * \param[out] out Output buffer. + * \param[in, out] out_len Output buffer length, output length. + * \param[in] style Value style. + * \param[in] first_value First value indication (multivalued support). + * \param[in] last_value Last value indication (multivalued support). + * + * \return Error code, KNOT_EOK if success. + */ +int yp_format_key1( + const yp_item_t *item, + const uint8_t *data, + size_t data_len, + char *out, + size_t out_len, + yp_style_t style, + bool first_value, + bool last_value +); + +/*! @} */ diff --git a/src/libknot/yparser/ypschema.c b/src/libknot/yparser/ypschema.c new file mode 100644 index 0000000..0da1ff1 --- /dev/null +++ b/src/libknot/yparser/ypschema.c @@ -0,0 +1,589 @@ +/* Copyright (C) 2019 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 <string.h> + +#include "libknot/yparser/ypschema.h" +#include "libknot/yparser/yptrafo.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" + +static size_t schema_count( + const yp_item_t *src) +{ + size_t count = 0; + for (const yp_item_t *item = src; item->name != NULL; item++) { + count++; + } + + return count; +} + +/*! Initializes the referenced item. */ +static int set_ref_item( + yp_item_t *dst, + const yp_item_t *schema) +{ + if (schema == NULL) { + return KNOT_EINVAL; + } + + // Get reference category. + const yp_name_t *ref_name = dst->var.r.ref_name; + const yp_item_t *ref = yp_schema_find(ref_name, NULL, schema); + if (ref == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + + dst->var.r.ref = ref; + + return KNOT_EOK; +} + +/*! Copies the sub_items list and initializes pointer to the identifier item. */ +static int set_grp_item( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Count subitems. + size_t count = schema_count(src->var.g.sub_items); + + // Allocate space for subitems + terminal zero item. + size_t memsize = (count + 1) * sizeof(yp_item_t); + dst->sub_items = malloc(memsize); + if (dst->sub_items == NULL) { + return KNOT_ENOMEM; + } + memset(dst->sub_items, 0, memsize); + + // Copy subitems. + for (size_t i = 0; i < count; i++) { + // The first item is an identifier if multi group. + if (i == 0 && (dst->flags & YP_FMULTI)) { + dst->var.g.id = &dst->sub_items[0]; + } + + // Copy sub-item. + dst->sub_items[i] = src->var.g.sub_items[i]; + + // Initialize sub-item. + int ret = KNOT_EOK; + switch (dst->sub_items[i].type) { + case YP_TREF: + ret = set_ref_item(dst->sub_items + i, schema); + break; + case YP_TGRP: // Deeper hierarchy is not supported. + ret = KNOT_ENOTSUP; + break; + default: + break; + } + + // Set the parent item. + dst->sub_items[i].parent = dst; + + if (ret != KNOT_EOK) { + free(dst->sub_items); + dst->sub_items = NULL; + return ret; + } + } + + if (src->flags & YP_FALLOC) { + dst->var.g.sub_items = malloc(memsize); + if (dst->var.g.sub_items == NULL) { + free(dst->sub_items); + dst->sub_items = NULL; + return KNOT_ENOMEM; + } + memcpy((void *)dst->var.g.sub_items, src->var.g.sub_items, memsize); + } + + return KNOT_EOK; +} + +static int set_item( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Check maximal item name length. + if ((uint8_t)src->name[0] > YP_MAX_ITEM_NAME_LEN) { + return KNOT_ERANGE; + } + + // Copy the static data. + *dst = *src; + + // Copy item name into dynamic memory. + if (src->flags & YP_FALLOC) { + dst->name = malloc(src->name[0] + 2); + if (dst->name == NULL) { + return KNOT_ENOMEM; + } + memcpy((void *)dst->name, src->name, src->name[0] + 2); + } + + int ret; + + // Item type specific preparation. + switch (src->type) { + case YP_TREF: + ret = set_ref_item(dst, schema); + break; + case YP_TGRP: + ret = set_grp_item(dst, src, schema); + break; + default: + ret = KNOT_EOK; + } + + if (ret != KNOT_EOK && src->flags & YP_FALLOC) { + free((void *)dst->name); + } + + return ret; +} + +static void unset_item( + yp_item_t *item) +{ + if (item->flags & YP_FALLOC) { + free((void *)item->name); + } + if (item->type & YP_TGRP) { + free(item->sub_items); + if (item->flags & YP_FALLOC) { + free((void *)item->var.g.sub_items); + } + } + + memset(item, 0, sizeof(yp_item_t)); +} + +static int schema_copy( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Copy the schema. + for (int i = 0; src[i].name != NULL; i++) { + int ret = set_item(&dst[i], &src[i], schema); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +_public_ +int yp_schema_copy( + yp_item_t **dst, + const yp_item_t *src) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + // Allocate space for new schema (+ terminal NULL item). + size_t size = (schema_count(src) + 1) * sizeof(yp_item_t); + yp_item_t *out = malloc(size); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, size); + + // Copy the schema. + int ret = schema_copy(out, src, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + *dst = out; + + return KNOT_EOK; +} + +_public_ +int yp_schema_merge( + yp_item_t **dst, + const yp_item_t *src1, + const yp_item_t *src2) +{ + if (dst == NULL || src1 == NULL || src2 == NULL) { + return KNOT_EINVAL; + } + + size_t count1 = schema_count(src1); + size_t count2 = schema_count(src2); + + // Allocate space for new schema (+ terminal NULL item). + size_t size = (count1 + count2 + 1) * sizeof(yp_item_t); + yp_item_t *out = malloc(size); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, size); + + // Copy the first schema. + int ret = schema_copy(out, src1, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + // Copy the second schema. + ret = schema_copy(out + count1, src2, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + *dst = out; + + return KNOT_EOK; +} + +_public_ +void yp_schema_purge_dynamic( + yp_item_t *schema) +{ + if (schema == NULL) { + return; + } + + for (yp_item_t *item = schema; item->name != NULL; item++) { + if (item->flags & YP_FALLOC) { + unset_item(item); + } + } +} + +_public_ +void yp_schema_free( + yp_item_t *schema) +{ + if (schema == NULL) { + return; + } + + for (yp_item_t *item = schema; item->name != NULL; item++) { + unset_item(item); + } + free(schema); +} + +/*! Search the schema for an item with the given name. */ +static const yp_item_t* find_item( + const char *name, + size_t name_len, + const yp_item_t *schema) +{ + if (name == NULL || schema == NULL) { + return NULL; + } + + for (const yp_item_t *item = schema; item->name != NULL; item++) { + if (item->name[0] != name_len) { + continue; + } + if (memcmp(item->name + 1, name, name_len) == 0) { + return item; + } + } + + return NULL; +} + +_public_ +const yp_item_t* yp_schema_find( + const yp_name_t *name, + const yp_name_t *parent_name, + const yp_item_t *schema) +{ + if (name == NULL || schema == NULL) { + return NULL; + } + + if (parent_name == NULL) { + return find_item(name + 1, name[0], schema); + } else { + const yp_item_t *parent = find_item(parent_name + 1, + parent_name[0], schema); + if (parent == NULL) { + return NULL; + } + return find_item(name + 1, name[0], parent->sub_items); + } +} + +_public_ +yp_check_ctx_t* yp_schema_check_init( + yp_item_t **schema) +{ + if (schema == NULL) { + return NULL; + } + + yp_check_ctx_t *ctx = malloc(sizeof(yp_check_ctx_t)); + if (ctx == NULL) { + return NULL; + } + memset(ctx, 0, sizeof(yp_check_ctx_t)); + + ctx->schema = schema; + + return ctx; +} + +static void reset_ctx( + yp_check_ctx_t *ctx, + size_t index) +{ + assert(index < YP_MAX_NODE_DEPTH); + + yp_node_t *node = &ctx->nodes[index]; + + node->parent = (index > 0) ? &ctx->nodes[index - 1] : NULL; + node->item = NULL; + node->id_len = 0; + node->data_len = 0; + + ctx->current = index; +} + +static int check_item( + const char *key, + size_t key_len, + const char *data, + size_t data_len, + yp_check_ctx_t *ctx, + bool allow_key1_without_id) +{ + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + bool is_id = false; + + if (parent != NULL) { + // Check for invalid indentation. + if (parent->item == NULL) { + return KNOT_YP_EINVAL_INDENT; + } + + // Check if valid group parent. + if (parent->item->type != YP_TGRP) { + return KNOT_YP_EINVAL_ITEM; + } + + // Check if valid subitem. + node->item = find_item(key, key_len, parent->item->sub_items); + } else { + node->item = find_item(key, key_len, *ctx->schema); + } + if (node->item == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + + // Check if the parent requires id specification. + if (parent != NULL && parent->item->var.g.id != NULL) { + // Check if id. + if (node->item == parent->item->var.g.id) { + is_id = true; + // Move current to the parent. + --(ctx->current); + // Check for missing id. + } else if (parent->id_len == 0 && !allow_key1_without_id) { + return KNOT_YP_ENOID; + } + } + + // Return if no data provided. + if (data == NULL) { + return KNOT_EOK; + } + + // Group cannot have data. + if (data_len != 0 && node->item->type == YP_TGRP) { + return KNOT_YP_ENOTSUP_DATA; + } + + // Convert item data to binary format. + const yp_item_t *item = (node->item->type != YP_TREF) ? + node->item : node->item->var.r.ref->var.g.id; + if (is_id) { + // Textual id must not be empty. + if (data_len == 0) { + return KNOT_YP_ENODATA; + } + + parent->id_len = sizeof(((yp_node_t *)NULL)->id); + int ret = yp_item_to_bin(item, data, data_len, parent->id, + &parent->id_len); + + // Binary id must not be empty. + if (ret == KNOT_EOK && parent->id_len == 0) { + return KNOT_YP_EINVAL_DATA; + } + + return ret; + } else { + node->data_len = sizeof(((yp_node_t *)NULL)->data); + int ret = yp_item_to_bin(item, data, data_len, node->data, + &node->data_len); + return ret; + } +} + +_public_ +int yp_schema_check_parser( + yp_check_ctx_t *ctx, + const yp_parser_t *parser) +{ + if (ctx == NULL || parser == NULL) { + return KNOT_EINVAL; + } + + int ret; + + switch (parser->event) { + case YP_EKEY0: + reset_ctx(ctx, 0); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + break; + case YP_EKEY1: + reset_ctx(ctx, 1); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + if (ret != KNOT_EOK) { + break; + } + + // Check for KEY1 event with id item. + if (ctx->current != 1) { + return KNOT_YP_ENOTSUP_ID; + } + + break; + case YP_EID: + reset_ctx(ctx, 1); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + if (ret != KNOT_EOK) { + break; + } + + // Check for ID event with nonid item. + if (ctx->current != 0) { + return KNOT_YP_EINVAL_ID; + } + + break; + default: + ret = KNOT_EPARSEFAIL; + break; + } + + return ret; +} + +_public_ +int yp_schema_check_str( + yp_check_ctx_t *ctx, + const char *key0, + const char *key1, + const char *id, + const char *data) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + size_t key0_len = (key0 != NULL) ? strlen(key0) : 0; + size_t key1_len = (key1 != NULL) ? strlen(key1) : 0; + size_t id_len = (id != NULL) ? strlen(id) : 0; + size_t data_len = (data != NULL) ? strlen(data) : 0; + + // Key0 must always be non-empty. + if (key0_len == 0) { + return KNOT_YP_EINVAL_ITEM; + } + + // Process key0. + reset_ctx(ctx, 0); + if (key1_len == 0) { + int ret = check_item(key0, key0_len, data, data_len, ctx, false); + if (ret != KNOT_EOK) { + return ret; + } + } else { + int ret = check_item(key0, key0_len, NULL, 0, ctx, false); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Process id. + if (id_len != 0) { + if (ctx->nodes[0].item->type != YP_TGRP || + ctx->nodes[0].item->var.g.id == NULL) { + return KNOT_YP_ENOTSUP_ID; + } + const yp_name_t *name = ctx->nodes[0].item->var.g.id->name; + + reset_ctx(ctx, 1); + int ret = check_item(name + 1, name[0], id, id_len, ctx, true); + if (ret != KNOT_EOK) { + return ret; + } + + // Check for non-id item (should not happen). + assert(ctx->current == 0); + + // Check for group id with data. + if (key1_len == 0 && data != NULL) { + return KNOT_YP_ENOTSUP_DATA; + } + } + + // Process key1. + if (key1_len != 0) { + reset_ctx(ctx, 1); + int ret = check_item(key1, key1_len, data, data_len, ctx, true); + if (ret != KNOT_EOK) { + return ret; + } + + // Check for id in key1 with extra data. + if (ctx->current != 1 && id_len != 0 && data != NULL) { + return KNOT_YP_ENOTSUP_DATA; + } + } + + return KNOT_EOK; +} + +_public_ +void yp_schema_check_deinit( + yp_check_ctx_t* ctx) +{ + free(ctx); +} diff --git a/src/libknot/yparser/ypschema.h b/src/libknot/yparser/ypschema.h new file mode 100644 index 0000000..35ee2ec --- /dev/null +++ b/src/libknot/yparser/ypschema.h @@ -0,0 +1,348 @@ +/* Copyright (C) 2017 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/>. + */ + +/*! + * \file + * + * \brief Schema layer for Yparser. + * + * \addtogroup yparser + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stddef.h> + +#include "libknot/yparser/yparser.h" +#include "libknot/lookup.h" + +struct wire_ctx; + +/*! Maximal length of item name. */ +#define YP_MAX_ITEM_NAME_LEN 64 +/*! Maximal length of binary identifier name (maximal dname length). */ +#define YP_MAX_ID_LEN 255 +/*! Maximal length of binary data (rough limit). */ +#define YP_MAX_DATA_LEN 32768 +/*! Integer item nil definition. */ +#define YP_NIL INT64_MIN +/*! Maximal number of miscellaneous callbacks/pointers. */ +#define YP_MAX_MISC_COUNT 4 +/*! Maximal node stack depth. */ +#define YP_MAX_NODE_DEPTH 2 + +#define YP_TXT_BIN_PARAMS struct wire_ctx *in, struct wire_ctx *out, const uint8_t *stop +#define YP_BIN_TXT_PARAMS struct wire_ctx *in, struct wire_ctx *out + +/*! Helper macros for item variables definition. */ +#define YP_VNONE .var.i = { 0 } +#define YP_VINT .var.i +#define YP_VBOOL .var.b +#define YP_VOPT .var.o +#define YP_VSTR .var.s +#define YP_VADDR .var.a +#define YP_VNET .var.d +#define YP_VDNAME .var.d +#define YP_VHEX .var.d +#define YP_VB64 .var.d +#define YP_VDATA .var.d +#define YP_VREF .var.r +#define YP_VGRP .var.g + +/*! Schema item name is a char string with a leading byte (string length). */ +typedef char yp_name_t; + +/*! Schema item type. */ +typedef enum { + YP_TNONE = 0, /*!< Unspecified. */ + YP_TINT, /*!< Integer. */ + YP_TBOOL, /*!< Boolean. */ + YP_TOPT, /*!< Option from the list. */ + YP_TSTR, /*!< String. */ + YP_THEX, /*!< String or hexadecimal string if "0x" prefix. */ + YP_TADDR, /*!< Address (address[\@port] or UNIX socket path). */ + YP_TNET, /*!< Network address range (address[/mask] or address-address). */ + YP_TDNAME, /*!< Domain name. */ + YP_TB64, /*!< Base64 encoded string. */ + YP_TDATA, /*!< Customized data. */ + YP_TREF, /*!< Reference to another item. */ + YP_TGRP, /*!< Group of sub-items. */ +} yp_type_t; + +/*! Schema item flags. */ +typedef enum { + YP_FNONE = 0, /*!< Unspecified. */ + YP_FMULTI = 1 << 0, /*!< Multivalued item. */ + YP_FALLOC = 1 << 1, /*!< Allocated item. */ + YP_FUSR1 = 1 << 5, /*!< User-defined flag1. */ + YP_FUSR2 = 1 << 6, /*!< User-defined flag2. */ + YP_FUSR3 = 1 << 7, /*!< User-defined flag3. */ + YP_FUSR4 = 1 << 8, /*!< User-defined flag4. */ + YP_FUSR5 = 1 << 9, /*!< User-defined flag5. */ + YP_FUSR6 = 1 << 10, /*!< User-defined flag6. */ + YP_FUSR7 = 1 << 11, /*!< User-defined flag7. */ + YP_FUSR8 = 1 << 12, /*!< User-defined flag8. */ + YP_FUSR9 = 1 << 13, /*!< User-defined flag9. */ + YP_FUSR10 = 1 << 14, /*!< User-defined flag10. */ + YP_FUSR11 = 1 << 15, /*!< User-defined flag11. */ + YP_FUSR12 = 1 << 16, /*!< User-defined flag12. */ + YP_FUSR13 = 1 << 17, /*!< User-defined flag13. */ + YP_FUSR14 = 1 << 18, /*!< User-defined flag14. */ + YP_FUSR15 = 1 << 19, /*!< User-defined flag15. */ + YP_FUSR16 = 1 << 20, /*!< User-defined flag16. */ +} yp_flag_t; + +/*! Schema item style. */ +typedef enum { + YP_SNONE = 0, /*!< Unspecified. */ + YP_SSIZE = 1 << 0, /*!< Size unit (B, K, M, G) (in, out). */ + YP_STIME = 1 << 1, /*!< Time unit (s, m, h, d) (in, out). */ + YP_SUNIT = YP_SSIZE | YP_STIME, /*!< Unit (in, out). */ + YP_SNOQUOTE = 1 << 2 /*!< Unquoted value (out). */ +} yp_style_t; + +typedef struct yp_item yp_item_t; + +/*! Schema item variables (type dependent). */ +typedef union { + /*! Integer variables. */ + struct { + /*! Minimal value. */ + int64_t min; + /*! Maximal value. */ + int64_t max; + /*! Default value. */ + int64_t dflt; + /*! Possible unit type. */ + yp_style_t unit; + } i; + /*! Boolen variables. */ + struct { + /*! Default value. */ + bool dflt; + } b; + /*! Option variables. */ + struct { + /*! List of options (maximal value is 255). */ + struct knot_lookup const *opts; + /*! Default value. */ + unsigned dflt; + } o; + /*! String variables. */ + struct { + /*! Default value. */ + char const *dflt; + } s; + /*! Address variables. */ + struct { + /*! Default port. */ + uint16_t dflt_port; + /*! Default socket. */ + char const *dflt_socket; + } a; + /*! Customized data variables. */ + struct { + /*! Length of default data. */ + size_t dflt_len; + /*! Default data. */ + uint8_t const *dflt; + /*! Text to binary transformation function. */ + int (*to_bin)(YP_TXT_BIN_PARAMS); + /*! Binary to text transformatio function. */ + int (*to_txt)(YP_BIN_TXT_PARAMS); + } d; + /*! Reference variables. */ + struct { + /*! Referenced group name. */ + yp_name_t const *ref_name; + /*! Referenced item (dynamic value). */ + yp_item_t const *ref; + } r; + /*! Group variables. */ + struct { + /*! List of sub-items. */ + yp_item_t const *sub_items; + /*! ID item of sub-items (dynamic value). */ + yp_item_t const *id; + } g; +} yp_var_t; + +/*! Schema item specification. */ +struct yp_item { + /*! Item name. */ + const yp_name_t *name; + /*! Item type. */ + yp_type_t type; + /*! Item parameters. */ + yp_var_t var; + /*! Item flags. */ + yp_flag_t flags; + /*! Arbitrary data/callbacks. */ + const void *misc[YP_MAX_MISC_COUNT]; + /*! Parent item. */ + yp_item_t *parent; + /*! Item group subitems (name=NULL terminated array). */ + yp_item_t *sub_items; +}; + +typedef struct yp_node yp_node_t; +struct yp_node { + /*! Parent node. */ + yp_node_t *parent; + /*! Node item descriptor. */ + const yp_item_t *item; + /*! Current binary id length. */ + size_t id_len; + /*! Current binary id. */ + uint8_t id[YP_MAX_ID_LEN]; + /*! Current item data length. */ + size_t data_len; + /*! Current item data. */ + uint8_t data[YP_MAX_DATA_LEN]; +}; + +/*! Context parameters for check operations. */ +typedef struct { + /*! Used schema. */ + yp_item_t **schema; + /*! Index of the current node. */ + size_t current; + /*! Node stack. */ + yp_node_t nodes[YP_MAX_NODE_DEPTH]; +} yp_check_ctx_t; + +/*! + * Copies the schema and reinitializes dynamic parameters. + * + * \param[out] dst New copy of the schema. + * \param[in] src Source schema. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_schema_copy( + yp_item_t **dst, + const yp_item_t *src +); + +/*! + * Merges two schemas. + * + * \param[out] dst Merged schema. + * \param[in] src1 Source schema1. + * \param[in] src2 Source schema2. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_schema_merge( + yp_item_t **dst, + const yp_item_t *src1, + const yp_item_t *src2 +); + +/*! + * Purges dynamic items from the schema. + * + * \param[in] schema Schema to purge. + */ +void yp_schema_purge_dynamic( + yp_item_t *schema +); + +/*! + * Deallocates the schema. + * + * \param[in] schema A schema returned by #yp_schema_copy(). + */ +void yp_schema_free( + yp_item_t *schema +); + +/*! + * Tries to find given parent_name/name in the schema. + * + * \param[in] name Name of the item. + * \param[in] parent_name Name of the parent item (NULL if no parent). + * \param[in] schema Schema. + * + * \return Item, NULL if not found or error. + */ +const yp_item_t* yp_schema_find( + const yp_name_t *name, + const yp_name_t *parent_name, + const yp_item_t *schema +); + +/*! + * Prepares a context for item check against the schema. + * + * \param[in] schema Schema. + * + * \return Context, NULL if error. + */ +yp_check_ctx_t* yp_schema_check_init( + yp_item_t **schema +); + +/*! + * Checks the current parser output against the schema. + * + * If the item is correct, context also contains binary value of the item. + * + * \param[in,out] ctx Check context. + * \param[in] parser Parser context. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_schema_check_parser( + yp_check_ctx_t *ctx, + const yp_parser_t *parser +); + +/*! + * Checks the string data against the schema. + * + * Description: key0[id].key1 data + * + * If the item is correct, context also contains binary value of the item. + * + * \param[in,out] ctx Check context. + * \param[in] key0 Key0 item name. + * \param[in] key1 Key1 item name. + * \param[in] id Item identifier. + * \param[in] data Item data (NULL means no data provided). + * + * \return Error code, KNOT_EOK if success. + */ +int yp_schema_check_str( + yp_check_ctx_t *ctx, + const char *key0, + const char *key1, + const char *id, + const char *data +); + +/*! + * Deallocates the context. + * + * \param[in,out] ctx Check context. + */ +void yp_schema_check_deinit( + yp_check_ctx_t *ctx +); + +/*! @} */ diff --git a/src/libknot/yparser/yptrafo.c b/src/libknot/yparser/yptrafo.c new file mode 100644 index 0000000..bb24a7a --- /dev/null +++ b/src/libknot/yparser/yptrafo.c @@ -0,0 +1,1098 @@ +/* Copyright (C) 2020 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 <arpa/inet.h> +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> + +#include "libknot/yparser/yptrafo.h" +#include "libknot/attribute.h" +#include "libknot/consts.h" +#include "libknot/dname.h" +#include "contrib/base64.h" +#include "contrib/ctype.h" +#include "contrib/sockaddr.h" +#include "contrib/wire_ctx.h" + +enum { + UNIT_BYTE = 'B', + UNIT_KILO = 'K', + UNIT_MEGA = 'M', + UNIT_GIGA = 'G', + UNIT_SEC = 's', + UNIT_MIN = 'm', + UNIT_HOUR = 'h', + UNIT_DAY = 'd' +}; + +enum { + MULTI_BYTE = 1, + MULTI_KILO = 1024, + MULTI_MEGA = 1024 * 1024, + MULTI_GIGA = 1024 * 1024 * 1024, + MULTI_SEC = 1, + MULTI_MIN = 60, + MULTI_HOUR = 3600, + MULTI_DAY = 24 * 3600 +}; + +static wire_ctx_t copy_in( + wire_ctx_t *in, + size_t in_len, + char *buf, + size_t buf_len) +{ + wire_ctx_t ctx = wire_ctx_init((uint8_t *)buf, buf_len); + wire_ctx_write(&ctx, in->position, in_len); + wire_ctx_skip(in, in_len); + // Write the terminator. + wire_ctx_write_u8(&ctx, '\0'); + wire_ctx_skip(&ctx, -1); + return ctx; +} + +_public_ +int yp_str_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + wire_ctx_write(out, in->position, YP_LEN); + wire_ctx_skip(in, YP_LEN); + // Write string terminator. + wire_ctx_write_u8(out, '\0'); + + YP_CHECK_RET; +} + +_public_ +int yp_str_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + size_t len = strlen((char *)in->position) + 1; + + wire_ctx_write(out, in->position, len); + wire_ctx_skip(in, len); + // Set the terminator as a current position. + wire_ctx_skip(out, -1); + + YP_CHECK_RET; +} + +_public_ +int yp_bool_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + if (strncasecmp((char *)in->position, "on", YP_LEN) == 0 || + strncasecmp((char *)in->position, "true", YP_LEN) == 0) { + wire_ctx_write_u8(out, 1); + } else if (strncasecmp((char *)in->position, "off", YP_LEN) == 0 || + strncasecmp((char *)in->position, "false", YP_LEN) == 0) { + wire_ctx_write_u8(out, 0); + } else { + return KNOT_EINVAL; + } + + wire_ctx_skip(in, YP_LEN); + + YP_CHECK_RET; +} + +_public_ +int yp_bool_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + const char *value; + + switch (wire_ctx_read_u8(in)) { + case 0: + value = "off"; + break; + case 1: + value = "on"; + break; + default: + return KNOT_EINVAL; + } + + int ret = snprintf((char *)out->position, wire_ctx_available(out), "%s", + value); + if (ret <= 0 || ret >= wire_ctx_available(out)) { + return KNOT_ESPACE; + } + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +static int remove_unit( + int64_t *number, + char unit, + yp_style_t style) +{ + int64_t multiplier = 1; + + // Get the multiplier for the unit. + if (style & YP_SSIZE) { + switch (unit) { + case UNIT_BYTE: + multiplier = MULTI_BYTE; + break; + case UNIT_KILO: + multiplier = MULTI_KILO; + break; + case UNIT_MEGA: + multiplier = MULTI_MEGA; + break; + case UNIT_GIGA: + multiplier = MULTI_GIGA; + break; + default: + return KNOT_EINVAL; + } + } else if (style & YP_STIME) { + switch (unit) { + case UNIT_SEC: + multiplier = MULTI_SEC; + break; + case UNIT_MIN: + multiplier = MULTI_MIN; + break; + case UNIT_HOUR: + multiplier = MULTI_HOUR; + break; + case UNIT_DAY: + multiplier = MULTI_DAY; + break; + default: + return KNOT_EINVAL; + } + } else { + return KNOT_EINVAL; + } + + // Check for possible number overflow. + if (INT64_MAX / multiplier < (*number >= 0 ? *number : -*number)) { + return KNOT_ERANGE; + } + + *number *= multiplier; + + return KNOT_EOK; +} + +_public_ +int yp_int_to_bin( + YP_TXT_BIN_PARAMS, + int64_t min, + int64_t max, + yp_style_t style) +{ + YP_CHECK_PARAMS_BIN; + + // Copy input string to the buffer to limit strtoll overread. + char buf[32]; + wire_ctx_t buf_ctx = copy_in(in, YP_LEN, buf, sizeof(buf)); + if (buf_ctx.error != KNOT_EOK) { + return buf_ctx.error; + } + + // Parse the number. + char *end; + errno = 0; + int64_t number = strtoll(buf, &end, 10); + + // Check for number overflow. + if (errno == ERANGE && (number == LLONG_MAX || number == LLONG_MIN)) { + return KNOT_ERANGE; + } + // Check if the whole string is invalid. + if ((errno != 0 && number == 0) || end == buf) { + return KNOT_EINVAL; + } + // Check the rest of the string for a unit. + if (*end != '\0') { + // Check just for one-char rest. + if (*(end + 1) != '\0') { + return KNOT_EINVAL; + } + + // Try to apply a unit on the number. + int ret = remove_unit(&number, *end, style); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Check for the result number overflow. + if (number < min || number > max) { + return KNOT_ERANGE; + } + + // Write the result. + wire_ctx_write_u64(out, number); + + YP_CHECK_RET; +} + +static void add_unit( + int64_t *number, + char *unit, + yp_style_t style) +{ + int64_t multiplier = 1; + char basic_unit = '\0'; + char new_unit = '\0'; + + // Get the multiplier for the unit. + if (style & YP_SSIZE) { + basic_unit = UNIT_BYTE; + + if (*number < MULTI_KILO) { + multiplier = MULTI_BYTE; + new_unit = UNIT_BYTE; + } else if (*number < MULTI_MEGA) { + multiplier = MULTI_KILO; + new_unit = UNIT_KILO; + } else if (*number < MULTI_GIGA) { + multiplier = MULTI_MEGA; + new_unit = UNIT_MEGA; + } else { + multiplier = MULTI_GIGA; + new_unit = UNIT_GIGA; + } + } else if (style & YP_STIME) { + basic_unit = UNIT_SEC; + + if (*number < MULTI_MIN) { + multiplier = MULTI_SEC; + new_unit = UNIT_SEC; + } else if (*number < MULTI_HOUR) { + multiplier = MULTI_MIN; + new_unit = UNIT_MIN; + } else if (*number < MULTI_DAY) { + multiplier = MULTI_HOUR; + new_unit = UNIT_HOUR; + } else { + multiplier = MULTI_DAY; + new_unit = UNIT_DAY; + } + } + + // Check for unit application without any remainder. + if ((*number % multiplier) == 0) { + *number /= multiplier; + *unit = new_unit; + } else { + *unit = basic_unit; + } +} + +_public_ +int yp_int_to_txt( + YP_BIN_TXT_PARAMS, + yp_style_t style) +{ + YP_CHECK_PARAMS_TXT; + + char unit[2] = { '\0' }; + int64_t number = wire_ctx_read_u64(in); + add_unit(&number, unit, style); + + int ret = snprintf((char *)out->position, wire_ctx_available(out), + "%"PRId64"%s", number, unit); + if (ret <= 0 || ret >= wire_ctx_available(out)) { + return KNOT_ESPACE; + } + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +static uint8_t sock_type_guess( + const uint8_t *str, + size_t len) +{ + size_t dots = 0; + size_t semicolons = 0; + size_t digits = 0; + + // Analyze the string. + for (size_t i = 0; i < len; i++) { + if (str[i] == '.') dots++; + else if (str[i] == ':') semicolons++; + else if (is_digit(str[i])) digits++; + } + + // Guess socket type. + if (semicolons >= 1) { + return 6; + } else if (semicolons == 0 && dots == 3 && digits >= 3) { + return 4; + } else { + return 0; + } +} + +_public_ +int yp_addr_noport_to_bin( + YP_TXT_BIN_PARAMS, + bool allow_unix) +{ + YP_CHECK_PARAMS_BIN; + + struct in_addr addr4; + struct in6_addr addr6; + + uint8_t type = sock_type_guess(in->position, YP_LEN); + + // Copy address to the buffer to limit inet_pton overread. + char buf[INET6_ADDRSTRLEN]; + if (type == 4 || type == 6) { + wire_ctx_t buf_ctx = copy_in(in, YP_LEN, buf, sizeof(buf)); + if (buf_ctx.error != KNOT_EOK) { + return buf_ctx.error; + } + } + + // Write address type. + wire_ctx_write_u8(out, type); + + // Write address as such. + if (type == 4 && inet_pton(AF_INET, buf, &addr4) == 1) { + wire_ctx_write(out, (uint8_t *)&(addr4.s_addr), + sizeof(addr4.s_addr)); + } else if (type == 6 && inet_pton(AF_INET6, buf, &addr6) == 1) { + wire_ctx_write(out, (uint8_t *)&(addr6.s6_addr), + sizeof(addr6.s6_addr)); + } else if (type == 0 && allow_unix) { + int ret = yp_str_to_bin(in, out, stop); + if (ret != KNOT_EOK) { + return ret; + } + } else { + return KNOT_EINVAL; + } + + YP_CHECK_RET; +} + +_public_ +int yp_addr_noport_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + struct in_addr addr4; + struct in6_addr addr6; + + int ret; + + switch (wire_ctx_read_u8(in)) { + case 0: + ret = yp_str_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + break; + case 4: + wire_ctx_read(in, &(addr4.s_addr), sizeof(addr4.s_addr)); + if (inet_ntop(AF_INET, &addr4, (char *)out->position, + wire_ctx_available(out)) == NULL) { + return KNOT_EINVAL; + } + wire_ctx_skip(out, strlen((char *)out->position)); + break; + case 6: + wire_ctx_read(in, &(addr6.s6_addr), sizeof(addr6.s6_addr)); + if (inet_ntop(AF_INET6, &addr6, (char *)out->position, + wire_ctx_available(out)) == NULL) { + return KNOT_EINVAL; + } + wire_ctx_skip(out, strlen((char *)out->position)); + break; + default: + return KNOT_EINVAL; + } + + YP_CHECK_RET; +} + +_public_ +int yp_addr_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Check for address@port separator. + const uint8_t *pos = (uint8_t *)strrchr((char *)in->position, '@'); + // Ignore out-of-bounds result. + if (pos >= stop) { + pos = NULL; + } + + // Store address type position. + uint8_t *type = out->position; + + // Write the address without a port. + int ret = yp_addr_noport_to_bin(in, out, pos, true); + if (ret != KNOT_EOK) { + return ret; + } + + if (pos != NULL) { + if (*type == 0) { + // Rewrite string terminator. + wire_ctx_skip(out, -1); + // Append the rest (separator and port) as a string. + ret = yp_str_to_bin(in, out, stop); + } else { + // Skip the separator. + wire_ctx_skip(in, sizeof(uint8_t)); + + // Write the port as a number. + ret = yp_int_to_bin(in, out, stop, 0, UINT16_MAX, YP_SNONE); + } + if (ret != KNOT_EOK) { + return ret; + } + } else if (*type == 4 || *type == 6) { + wire_ctx_write_u64(out, (uint64_t)-1); + } + + YP_CHECK_RET; +} + +_public_ +int yp_addr_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Store address type position. + uint8_t *type = in->position; + + // Write address. + int ret = yp_addr_noport_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + + // Write port. + if (*type == 4 || *type == 6) { + int64_t port = wire_ctx_read_u64(in); + + if (port >= 0) { + // Write separator. + wire_ctx_write_u8(out, '@'); + + // Write port. + wire_ctx_skip(in, -sizeof(uint64_t)); + ret = yp_int_to_txt(in, out, YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + YP_CHECK_RET; +} + +_public_ +int yp_addr_range_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Format: 0 - single address, 1 - address prefix, 2 - address range. + uint8_t format = 0; + + // Check for the "addr/mask" format. + const uint8_t *pos = (uint8_t *)strchr((char *)in->position, '/'); + if (pos >= stop) { + pos = NULL; + } + + if (pos != NULL) { + format = 1; + } else { + // Check for the "addr1-addr2" format. + pos = (uint8_t *)strchr((char *)in->position, '-'); + if (pos >= stop) { + pos = NULL; + } + if (pos != NULL) { + format = 2; + } + } + + // Store address1 type position. + uint8_t *type1 = out->position; + + // Write the first address. + int ret = yp_addr_noport_to_bin(in, out, pos, false); + if (ret != KNOT_EOK) { + return ret; + } + + wire_ctx_write_u8(out, format); + + switch (format) { + case 1: + // Skip the separator. + wire_ctx_skip(in, sizeof(uint8_t)); + + // Write the prefix length. + ret = yp_int_to_bin(in, out, stop, 0, (*type1 == 4) ? 32 : 128, + YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + break; + case 2: + // Skip the separator. + wire_ctx_skip(in, sizeof(uint8_t)); + + // Store address2 type position. + uint8_t *type2 = out->position; + + // Write the second address. + ret = yp_addr_noport_to_bin(in, out, stop, false); + if (ret != KNOT_EOK) { + return ret; + } + + // Check for address mismatch. + if (*type1 != *type2) { + return KNOT_EINVAL; + } + break; + default: + break; + } + + YP_CHECK_RET; +} + +_public_ +int yp_addr_range_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Write the first address. + int ret = yp_addr_noport_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + + uint8_t format = wire_ctx_read_u8(in); + + switch (format) { + case 1: + // Write the separator. + wire_ctx_write_u8(out, '/'); + + // Write the prefix length. + ret = yp_int_to_txt(in, out, YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + break; + case 2: + // Write the separator. + wire_ctx_write_u8(out, '-'); + + // Write the second address. + ret = yp_addr_noport_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + break; + default: + break; + } + + YP_CHECK_RET; +} + +_public_ +int yp_option_to_bin( + YP_TXT_BIN_PARAMS, + const knot_lookup_t *opts) +{ + YP_CHECK_PARAMS_BIN; + + while (opts->name != NULL) { + if (YP_LEN == strlen(opts->name) && + strncasecmp((char *)in->position, opts->name, YP_LEN) == 0) { + wire_ctx_write_u8(out, opts->id); + wire_ctx_skip(in, YP_LEN); + YP_CHECK_RET; + } + opts++; + } + + return KNOT_EINVAL; +} + +_public_ +int yp_option_to_txt( + YP_BIN_TXT_PARAMS, + const knot_lookup_t *opts) +{ + uint8_t id = wire_ctx_read_u8(in); + + while (opts->name != NULL) { + if (id == opts->id) { + int ret = snprintf((char *)out->position, + wire_ctx_available(out), "%s", + opts->name); + if (ret <= 0 || ret >= wire_ctx_available(out)) { + return KNOT_ESPACE; + } + wire_ctx_skip(out, ret); + YP_CHECK_RET; + } + opts++; + } + + return KNOT_EINVAL; +} + +_public_ +int yp_dname_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Copy dname string to the buffer to limit dname_from_str overread. + knot_dname_txt_storage_t buf; + wire_ctx_t buf_ctx = copy_in(in, YP_LEN, buf, sizeof(buf)); + if (buf_ctx.error != KNOT_EOK) { + return buf_ctx.error; + } + + // Convert the dname. + if (knot_dname_from_str(out->position, buf, wire_ctx_available(out)) == NULL) { + return KNOT_EINVAL; + } + + // Check the result and count the length. + int ret = knot_dname_wire_check(out->position, + out->position + wire_ctx_available(out), + NULL); + if (ret <= 0) { + return KNOT_EINVAL; + } + + // Convert the result to lower case. + knot_dname_to_lower(out->position); + + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +_public_ +int yp_dname_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + if (knot_dname_to_str((char *)out->position, in->position, + wire_ctx_available(out)) == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_skip(out, strlen((char *)out->position)); + + YP_CHECK_RET; +} + +static int hex_to_num(char hex) { + if (hex >= '0' && hex <= '9') return hex - '0'; + if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10; + if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10; + return -1; +} + +_public_ +int yp_hex_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Check for hex notation (leading "0x"). + if (wire_ctx_available(in) >= 2 && + in->position[0] == '0' && in->position[1] == 'x') { + wire_ctx_skip(in, 2); + + if (YP_LEN % 2 != 0) { + return KNOT_EINVAL; + } + + // Write data length. + wire_ctx_write_u16(out, YP_LEN / 2); + + // Decode hex string. + while (YP_LEN > 0) { + uint8_t buf[2] = { 0 }; + wire_ctx_read(in, buf, sizeof(buf)); + + if (!is_xdigit(buf[0]) || + !is_xdigit(buf[1])) { + return KNOT_EINVAL; + } + + wire_ctx_write_u8(out, 16 * hex_to_num(buf[0]) + + hex_to_num(buf[1])); + } + } else { + // Write data length. + wire_ctx_write_u16(out, YP_LEN); + + // Write textual string (without terminator). + wire_ctx_write(out, in->position, YP_LEN); + wire_ctx_skip(in, YP_LEN); + } + + YP_CHECK_RET; +} + +_public_ +int yp_hex_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + size_t len = wire_ctx_read_u16(in); + + bool printable = true; + + // Check for printable string. + for (size_t i = 0; i < len; i++) { + if (!is_print(in->position[i])) { + printable = false; + break; + } + } + + if (printable) { + wire_ctx_write(out, in->position, len); + wire_ctx_skip(in, len); + } else { + const char *prefix = "0x"; + const char *hex = "0123456789ABCDEF"; + + // Write hex prefix. + wire_ctx_write(out, (uint8_t *)prefix, strlen(prefix)); + + // Encode data to hex. + for (size_t i = 0; i < len; i++) { + uint8_t bin = wire_ctx_read_u8(in); + wire_ctx_write_u8(out, hex[bin / 16]); + wire_ctx_write_u8(out, hex[bin % 16]); + } + } + + // Write the terminator. + wire_ctx_write_u8(out, '\0'); + wire_ctx_skip(out, -1); + + YP_CHECK_RET; +} + +_public_ +int yp_base64_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Reserve some space for data length. + wire_ctx_skip(out, sizeof(uint16_t)); + + int ret = knot_base64_decode(in->position, YP_LEN, out->position, + wire_ctx_available(out)); + if (ret < 0) { + return ret; + } + wire_ctx_skip(in, YP_LEN); + + // Write the data length. + wire_ctx_skip(out, -sizeof(uint16_t)); + wire_ctx_write_u16(out, ret); + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +_public_ +int yp_base64_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Read the data length. + uint16_t len = wire_ctx_read_u16(in); + + int ret = knot_base64_encode(in->position, len, out->position, + wire_ctx_available(out)); + if (ret < 0) { + return ret; + } + wire_ctx_skip(out, ret); + + // Write the terminator. + wire_ctx_write_u8(out, '\0'); + wire_ctx_skip(out, -1); + + YP_CHECK_RET; +} + +_public_ +int yp_item_to_bin( + const yp_item_t *item, + const char *txt, + size_t txt_len, + uint8_t *bin, + size_t *bin_len) +{ + if (item == NULL || txt == NULL || bin == NULL || bin_len == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_t in = wire_ctx_init_const((const uint8_t *)txt, txt_len); + wire_ctx_t out = wire_ctx_init(bin, *bin_len); + + int ret; + size_t ref_len; + + switch (item->type) { + case YP_TINT: + ret = yp_int_to_bin(&in, &out, NULL, item->var.i.min, + item->var.i.max, item->var.i.unit); + break; + case YP_TBOOL: + ret = yp_bool_to_bin(&in, &out, NULL); + break; + case YP_TOPT: + ret = yp_option_to_bin(&in, &out, NULL, item->var.o.opts); + break; + case YP_TSTR: + ret = yp_str_to_bin(&in, &out, NULL); + break; + case YP_TADDR: + ret = yp_addr_to_bin(&in, &out, NULL); + break; + case YP_TNET: + ret = yp_addr_range_to_bin(&in, &out, NULL); + break; + case YP_TDNAME: + ret = yp_dname_to_bin(&in, &out, NULL); + break; + case YP_THEX: + ret = yp_hex_to_bin(&in, &out, NULL); + break; + case YP_TB64: + ret = yp_base64_to_bin(&in, &out, NULL); + break; + case YP_TDATA: + ret = item->var.d.to_bin(&in, &out, NULL); + break; + case YP_TREF: + ref_len = wire_ctx_available(&out); + ret = yp_item_to_bin(item->var.r.ref->var.g.id, + (char *)in.position, wire_ctx_available(&in), + out.position, &ref_len); + wire_ctx_skip(&out, ref_len); + break; + default: + ret = KNOT_EOK; + } + + if (ret != KNOT_EOK) { + return ret; + } else if (in.error != KNOT_EOK) { + return in.error; + } else if (out.error != KNOT_EOK) { + return out.error; + } + + *bin_len = wire_ctx_offset(&out); + + return KNOT_EOK; +} + +_public_ +int yp_item_to_txt( + const yp_item_t *item, + const uint8_t *bin, + size_t bin_len, + char *txt, + size_t *txt_len, + yp_style_t style) +{ + if (item == NULL || bin == NULL || txt == NULL || txt_len == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_t in = wire_ctx_init_const(bin, bin_len); + wire_ctx_t out = wire_ctx_init((uint8_t *)txt, *txt_len); + + // Write leading quote. + if (!(style & YP_SNOQUOTE)) { + wire_ctx_write_u8(&out, '"'); + } + + int ret; + size_t ref_len; + + switch (item->type) { + case YP_TINT: + ret = yp_int_to_txt(&in, &out, item->var.i.unit & style); + break; + case YP_TBOOL: + ret = yp_bool_to_txt(&in, &out); + break; + case YP_TOPT: + ret = yp_option_to_txt(&in, &out, item->var.o.opts); + break; + case YP_TSTR: + ret = yp_str_to_txt(&in, &out); + break; + case YP_TADDR: + ret = yp_addr_to_txt(&in, &out); + break; + case YP_TNET: + ret = yp_addr_range_to_txt(&in, &out); + break; + case YP_TDNAME: + ret = yp_dname_to_txt(&in, &out); + break; + case YP_THEX: + ret = yp_hex_to_txt(&in, &out); + break; + case YP_TB64: + ret = yp_base64_to_txt(&in, &out); + break; + case YP_TDATA: + ret = item->var.d.to_txt(&in, &out); + break; + case YP_TREF: + ref_len = wire_ctx_available(&out); + ret = yp_item_to_txt(item->var.r.ref->var.g.id, + in.position, wire_ctx_available(&in), + (char *)out.position, + &ref_len, style | YP_SNOQUOTE); + wire_ctx_skip(&out, ref_len); + break; + default: + ret = KNOT_EOK; + } + + // Write trailing quote. + if (!(style & YP_SNOQUOTE)) { + wire_ctx_write_u8(&out, '"'); + } + + // Write string terminator. + wire_ctx_write_u8(&out, '\0'); + wire_ctx_skip(&out, -1); + + if (ret != KNOT_EOK) { + return ret; + } else if (in.error != KNOT_EOK) { + return in.error; + } else if (out.error != KNOT_EOK) { + return out.error; + } + + *txt_len = wire_ctx_offset(&out); + + return KNOT_EOK; +} + +_public_ +struct sockaddr_storage yp_addr_noport( + const uint8_t *data) +{ + struct sockaddr_storage ss = { AF_UNSPEC }; + + // Read address type. + uint8_t type = *data; + data += sizeof(type); + + // Set address. + switch (type) { + case 0: + sockaddr_set(&ss, AF_UNIX, (char *)data, 0); + break; + case 4: + sockaddr_set_raw(&ss, AF_INET, data, + sizeof(((struct in_addr *)NULL)->s_addr)); + break; + case 6: + sockaddr_set_raw(&ss, AF_INET6, data, + sizeof(((struct in6_addr *)NULL)->s6_addr)); + break; + } + + return ss; +} + +_public_ +struct sockaddr_storage yp_addr( + const uint8_t *data, + bool *no_port) +{ + struct sockaddr_storage ss = yp_addr_noport(data); + + size_t addr_len; + + // Get binary address length. + switch (ss.ss_family) { + case AF_INET: + addr_len = sizeof(((struct in_addr *)NULL)->s_addr); + break; + case AF_INET6: + addr_len = sizeof(((struct in6_addr *)NULL)->s6_addr); + break; + default: + addr_len = 0; + *no_port = true; + } + + if (addr_len > 0) { + int64_t port = knot_wire_read_u64(data + sizeof(uint8_t) + addr_len); + if (port >= 0) { + sockaddr_port_set(&ss, port); + *no_port = false; + } else { + *no_port = true; + } + } + + return ss; +} diff --git a/src/libknot/yparser/yptrafo.h b/src/libknot/yparser/yptrafo.h new file mode 100644 index 0000000..285ab98 --- /dev/null +++ b/src/libknot/yparser/yptrafo.h @@ -0,0 +1,311 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Value transformations for Yparser. + * + * \addtogroup yparser + * @{ + */ + +#pragma once + +#include "libknot/yparser/ypschema.h" +#include "libknot/lookup.h" +#include "libknot/wire.h" + +/*! + * Transforms textual item value to binary form. + * + * \param[in] item Schema item to transform. + * \param[in] txt Value to transform. + * \param[in] txt_len Value length. + * \param[out] bin Output buffer. + * \param[in, out] bin_len Output buffer length, output length. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_item_to_bin( + const yp_item_t *item, + const char *txt, + size_t txt_len, + uint8_t *bin, + size_t *bin_len +); + +/*! + * Transforms binary item value to textual form. + * + * \param[in] item Schema item to transform. + * \param[in] bin Value to transform. + * \param[in] bin_len Value length. + * \param[out] txt Output buffer. + * \param[in, out] txt_len Output buffer length, output length. + * \param[in] style Value style. + * + * \return Error code, KNOT_EOK if success. + */ +int yp_item_to_txt( + const yp_item_t *item, + const uint8_t *bin, + size_t bin_len, + char *txt, + size_t *txt_len, + yp_style_t style +); + +/*! + * Converts binary value to string pointer. + * + * \param[in] data Binary value to transform. + * + * \return String pointer. + */ +inline static const char* yp_str( + const uint8_t *data) +{ + return (const char *)data; +} + +/*! + * Converts binary value to boolean value. + * + * \param[in] data Binary value to transform. + * + * \return Boolean value. + */ +inline static bool yp_bool( + const uint8_t *data) +{ + return (data[0] == 0) ? false : true; +} + +/*! + * Converts binary value to integer value. + * + * \param[in] data Binary value to transform. + * + * \return Integer value. + */ +inline static int64_t yp_int( + const uint8_t *data) +{ + return (int64_t)knot_wire_read_u64(data); +} + +/*! + * Converts binary value to address value. + * + * \param[in] data Binary value to transform. + * + * \return Address value. + */ +struct sockaddr_storage yp_addr_noport( + const uint8_t *data +); + +/*! + * Converts binary value to address value with an optional port. + * + * \param[in] data Binary value to transform. + * \param[out] no_port No port indicator. + * + * \return Address value. + */ +struct sockaddr_storage yp_addr( + const uint8_t *data, + bool *no_port +); + +/*! + * Converts binary value to option value. + * + * \param[in] data Binary value to transform. + * + * \return Unsigned value. + */ +inline static unsigned yp_opt( + const uint8_t *data) +{ + return (unsigned)data[0]; +} + +/*! + * Converts binary value to dname pointer. + * + * \param[in] data Binary value to transform. + * + * \return Dname pointer. + */ +inline static const uint8_t* yp_dname( + const uint8_t *data) +{ + return data; +} + +/*! + * Converts binary value to data pointer. + * + * Applies to all data types with 2-byte length prefix (YP_THEX, YP_TB64). + * + * \param[in] data Binary value to transform. + * + * \return Data pointer. + */ +inline static const uint8_t* yp_bin( + const uint8_t *data) +{ + return data + 2; +} + +/*! + * Gets binary value length. + * + * Applies to all data types with 2-byte length prefix (YP_THEX, YP_TB64). + * + * \param[in] data Binary value to transform. + * + * \return Data length. + */ +inline static size_t yp_bin_len( + const uint8_t *data) +{ + return knot_wire_read_u16(data); +} + +/*! + * \brief Helper macros for conversion functions. + */ + +#define YP_CHECK_CTX \ + if (in->error != KNOT_EOK) { \ + return in->error; \ + } else if (out->error != KNOT_EOK) { \ + return out->error; \ + } \ + +#define YP_CHECK_STOP \ + if (stop != NULL) { \ + assert(stop <= in->position + wire_ctx_available(in)); \ + } else { \ + stop = in->position + wire_ctx_available(in); \ + } + +#define YP_LEN (stop - in->position) + +#define YP_CHECK_PARAMS_BIN \ + YP_CHECK_CTX YP_CHECK_STOP + +#define YP_CHECK_PARAMS_TXT \ + YP_CHECK_CTX + +#define YP_CHECK_RET \ + YP_CHECK_CTX return KNOT_EOK; + +/*! + * \brief Conversion functions for basic types. + */ + +int yp_str_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_str_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_bool_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_bool_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_int_to_bin( + YP_TXT_BIN_PARAMS, + int64_t min, + int64_t max, + yp_style_t style +); + +int yp_int_to_txt( + YP_BIN_TXT_PARAMS, + yp_style_t style +); + +int yp_addr_noport_to_bin( + YP_TXT_BIN_PARAMS, + bool allow_unix +); + +int yp_addr_noport_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_addr_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_addr_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_addr_range_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_addr_range_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_option_to_bin( + YP_TXT_BIN_PARAMS, + const struct knot_lookup *opts +); + +int yp_option_to_txt( + YP_BIN_TXT_PARAMS, + const struct knot_lookup *opts +); + +int yp_dname_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_dname_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_hex_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_hex_to_txt( + YP_BIN_TXT_PARAMS +); + +int yp_base64_to_bin( + YP_TXT_BIN_PARAMS +); + +int yp_base64_to_txt( + YP_BIN_TXT_PARAMS +); + +/*! @} */ |