summaryrefslogtreecommitdiffstats
path: root/src/libknot
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot')
-rw-r--r--src/libknot/Makefile.inc97
-rw-r--r--src/libknot/attribute.h47
-rw-r--r--src/libknot/codes.c81
-rw-r--r--src/libknot/codes.h50
-rw-r--r--src/libknot/consts.h159
-rw-r--r--src/libknot/control/control.c564
-rw-r--r--src/libknot/control/control.h159
-rw-r--r--src/libknot/cookies.c170
-rw-r--r--src/libknot/cookies.h103
-rw-r--r--src/libknot/db/db.h92
-rw-r--r--src/libknot/db/db_lmdb.c581
-rw-r--r--src/libknot/db/db_lmdb.h73
-rw-r--r--src/libknot/db/db_trie.c178
-rw-r--r--src/libknot/db/db_trie.h40
-rw-r--r--src/libknot/descriptor.c423
-rw-r--r--src/libknot/descriptor.h305
-rw-r--r--src/libknot/dname.c781
-rw-r--r--src/libknot/dname.h344
-rw-r--r--src/libknot/endian.h51
-rw-r--r--src/libknot/errcode.h265
-rw-r--r--src/libknot/error.c232
-rw-r--r--src/libknot/error.h50
-rw-r--r--src/libknot/libknot.h71
-rw-r--r--src/libknot/libknot.h.in71
-rw-r--r--src/libknot/lookup.h89
-rw-r--r--src/libknot/mm_ctx.h41
-rw-r--r--src/libknot/packet/compr.h101
-rw-r--r--src/libknot/packet/pkt.c825
-rw-r--r--src/libknot/packet/pkt.h404
-rw-r--r--src/libknot/packet/rrset-wire.c723
-rw-r--r--src/libknot/packet/rrset-wire.h69
-rw-r--r--src/libknot/packet/wire.h1045
-rw-r--r--src/libknot/rdata.h101
-rw-r--r--src/libknot/rdataset.c321
-rw-r--r--src/libknot/rdataset.h195
-rw-r--r--src/libknot/rrset-dump.c2108
-rw-r--r--src/libknot/rrset-dump.h113
-rw-r--r--src/libknot/rrset.c217
-rw-r--r--src/libknot/rrset.h194
-rw-r--r--src/libknot/rrtype/dnskey.h72
-rw-r--r--src/libknot/rrtype/ds.h64
-rw-r--r--src/libknot/rrtype/naptr.c47
-rw-r--r--src/libknot/rrtype/naptr.h41
-rw-r--r--src/libknot/rrtype/nsec.h50
-rw-r--r--src/libknot/rrtype/nsec3.h98
-rw-r--r--src/libknot/rrtype/nsec3param.h64
-rw-r--r--src/libknot/rrtype/opt.c721
-rw-r--r--src/libknot/rrtype/opt.h622
-rw-r--r--src/libknot/rrtype/rdname.h94
-rw-r--r--src/libknot/rrtype/rrsig.h100
-rw-r--r--src/libknot/rrtype/soa.h94
-rw-r--r--src/libknot/rrtype/tsig.c404
-rw-r--r--src/libknot/rrtype/tsig.h122
-rw-r--r--src/libknot/tsig-op.c683
-rw-r--r--src/libknot/tsig-op.h187
-rw-r--r--src/libknot/tsig.c186
-rw-r--r--src/libknot/tsig.h89
-rw-r--r--src/libknot/version.h25
-rw-r--r--src/libknot/version.h.in25
-rw-r--r--src/libknot/wire.h154
-rw-r--r--src/libknot/xdp/Makefile.am15
-rw-r--r--src/libknot/xdp/Makefile.in533
-rw-r--r--src/libknot/xdp/bpf-consts.h23
-rw-r--r--src/libknot/xdp/bpf-kernel-obj.c144
-rw-r--r--src/libknot/xdp/bpf-kernel-obj.h2
-rw-r--r--src/libknot/xdp/bpf-kernel.c159
-rw-r--r--src/libknot/xdp/bpf-user.c287
-rw-r--r--src/libknot/xdp/bpf-user.h112
-rw-r--r--src/libknot/xdp/eth.c129
-rw-r--r--src/libknot/xdp/eth.h57
-rw-r--r--src/libknot/xdp/xdp.c717
-rw-r--r--src/libknot/xdp/xdp.h167
-rw-r--r--src/libknot/yparser/yparser.c178
-rw-r--r--src/libknot/yparser/yparser.h149
-rw-r--r--src/libknot/yparser/ypbody.c461
-rw-r--r--src/libknot/yparser/ypformat.c121
-rw-r--r--src/libknot/yparser/ypformat.h101
-rw-r--r--src/libknot/yparser/ypschema.c589
-rw-r--r--src/libknot/yparser/ypschema.h348
-rw-r--r--src/libknot/yparser/yptrafo.c1098
-rw-r--r--src/libknot/yparser/yptrafo.h311
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(&timestamp, &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, &copy.secret) != DNSSEC_EOK) {
+ knot_tsig_key_deinit(&copy);
+ 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 *)&eth->h_source;
+ out->eth_to = (void *)&eth->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 *)&eth->h_source;
+ msg->eth_to = (void *)&eth->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
+);
+
+/*! @} */