diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
commit | f449f278dd3c70e479a035f50a9bb817a9b433ba (patch) | |
tree | 8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /src/libknot | |
parent | Initial commit. (diff) | |
download | knot-upstream/3.2.6.tar.xz knot-upstream/3.2.6.zip |
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libknot')
99 files changed, 27503 insertions, 0 deletions
diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc new file mode 100755 index 0000000..73a4d6e --- /dev/null +++ b/src/libknot/Makefile.inc @@ -0,0 +1,136 @@ +lib_LTLIBRARIES += libknot.la +pkgconfig_DATA += libknot.pc + +libknot_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(lmdb_CFLAGS) \ + ${fuzzer_CFLAGS} +libknot_la_LDFLAGS = $(AM_LDFLAGS) $(libknot_VERSION_INFO) $(LDFLAG_EXCLUDE_LIBS) \ + ${fuzzer_LDFLAGS} +libknot_la_LIBADD = libdnssec.la $(libcontrib_LIBS) $(lmdb_LIBS) $(math_LIBS) + +if EMBEDDED_LIBNGTCP2 +libknot_la_LIBADD += $(libembngtcp2_LIBS) +endif EMBEDDED_LIBNGTCP2 + +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/dynarray.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/probe/data.h \ + libknot/probe/probe.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/svcb.h \ + libknot/rrtype/tsig.h \ + libknot/rrtype/zonemd.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/xdp/tcp_iobuf.h \ + libknot/xdp.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/probe/data.c \ + libknot/probe/probe.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 \ + libknot/xdp/tcp_iobuf.c + +if EMBEDDED_LIBBPF +libknot_la_LIBADD += $(libembbpf_LIBS) +endif EMBEDDED_LIBBPF +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/msg.h \ + libknot/xdp/tcp.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/msg_init.h \ + libknot/xdp/protocols.h \ + libknot/xdp/tcp.c \ + libknot/xdp/xdp.c + +if ENABLE_QUIC + +libknot_la_CPPFLAGS += $(libngtcp2_CFLAGS) $(gnutls_CFLAGS) +libknot_la_LIBADD += $(libngtcp2_LIBS) $(gnutls_LIBS) + +nobase_include_libknot_HEADERS += \ + libknot/xdp/quic.h \ + libknot/xdp/quic_conn.h + +libknot_la_SOURCES += \ + libknot/xdp/quic.c \ + libknot/xdp/quic_conn.c + +endif ENABLE_QUIC +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..525aef3 --- /dev/null +++ b/src/libknot/attribute.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2021 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 and variable attributes. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +/*! \brief Library visibility macros. */ +#define _public_ __attribute__((visibility("default"))) +#define _hidden_ __attribute__((visibility("hidden"))) + +#define _unused_ __attribute__((unused)) + +#define _cleanup_(var) __attribute__((cleanup(var))) + +/*! \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..e6b38cf --- /dev/null +++ b/src/libknot/codes.c @@ -0,0 +1,129 @@ +/* Copyright (C) 2023 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" +#include "libdnssec/key.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_edns_ede_names[] = { + { 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" }, + { KNOT_EDNS_EDE_EXPIRED_INV, "Signature Expired before Valid" }, + { KNOT_EDNS_EDE_TOO_EARLY, "Too Early" }, + { KNOT_EDNS_EDE_NSEC3_ITERS, "Unsupported NSEC3 Iterations Value" }, + { KNOT_EDNS_EDE_NONCONF_POLICY, "Unable to conform to policy" }, + { KNOT_EDNS_EDE_SYNTHESIZED, "Synthesized" }, + { 0, NULL } +}; + +_public_ +const knot_lookup_t knot_dnssec_alg_names[] = { + { DNSSEC_KEY_ALGORITHM_DELETE, "DELETE" }, + { DNSSEC_KEY_ALGORITHM_RSA_MD5, "RSAMD5" }, + { DNSSEC_KEY_ALGORITHM_DH, "DH" }, + { DNSSEC_KEY_ALGORITHM_DSA, "DSA" }, + { DNSSEC_KEY_ALGORITHM_RSA_SHA1, "RSASHA1" }, + { DNSSEC_KEY_ALGORITHM_DSA_NSEC3_SHA1, "DSA_NSEC3_SHA1" }, + { DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3, "RSASHA1_NSEC3_SHA1" }, + { DNSSEC_KEY_ALGORITHM_RSA_SHA256, "RSASHA256" }, + { DNSSEC_KEY_ALGORITHM_RSA_SHA512, "RSASHA512" }, + { DNSSEC_KEY_ALGORITHM_ECC_GOST, "ECC_GOST" }, + { DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256, "ECDSAP256SHA256" }, + { DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384, "ECDSAP384SHA384" }, + { DNSSEC_KEY_ALGORITHM_ED25519, "ED25519" }, + { DNSSEC_KEY_ALGORITHM_ED448, "ED448" }, + { DNSSEC_KEY_ALGORITHM_INDIRECT, "INDIRECT" }, + { DNSSEC_KEY_ALGORITHM_PRIVATEDNS, "PRIVATEDNS" }, + { DNSSEC_KEY_ALGORITHM_PRIVATEOID, "PRIVATEOID" }, + { 0, NULL } +}; + +_public_ +const knot_lookup_t knot_svcb_param_names[] = { + { KNOT_SVCB_PARAM_MANDATORY, "mandatory" }, + { KNOT_SVCB_PARAM_ALPN, "alpn" }, + { KNOT_SVCB_PARAM_NDALPN, "no-default-alpn" }, + { KNOT_SVCB_PARAM_PORT, "port" }, + { KNOT_SVCB_PARAM_IPV4HINT, "ipv4hint" }, + { KNOT_SVCB_PARAM_ECH, "ech" }, + { KNOT_SVCB_PARAM_IPV6HINT, "ipv6hint" }, + { 0, NULL } +}; diff --git a/src/libknot/codes.h b/src/libknot/codes.h new file mode 100644 index 0000000..2d3a548 --- /dev/null +++ b/src/libknot/codes.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2022 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 EDNS EDE names. + */ +extern const knot_lookup_t knot_edns_ede_names[]; + +/*! + * \brief DNSSEC algorithm names. + */ +extern const knot_lookup_t knot_dnssec_alg_names[]; + +/*! + * \brief Service binding (SVCB) param types. + */ +extern const knot_lookup_t knot_svcb_param_names[]; + +/*! @} */ diff --git a/src/libknot/consts.h b/src/libknot/consts.h new file mode 100644 index 0000000..e6041b7 --- /dev/null +++ b/src/libknot/consts.h @@ -0,0 +1,163 @@ +/* Copyright (C) 2023 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. + * + * Binary: 3 x (0x3F + maximum_label) + (0x3D + rest_label) + (0x00) + * Textual: 3 x (maximum_label + '.') + (rest_label + '.') + * + * Each dname label byte takes 4 characters (\\DDD). + * + * KNOT_DNAME_TXT_MAXLEN = 3 x (63 x 4 + 1) + (61 x 4 + 1) + */ +#define KNOT_DNAME_TXT_MAXLEN 1004 + +/*! + * \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 Extended error codes as in EDNS option #15. + * + * \note The default -1 value must be filtered out before storing to uint16_t! + * + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#extended-dns-error-codes + */ +typedef enum { + KNOT_EDNS_EDE_NONE = -1, + 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, + KNOT_EDNS_EDE_EXPIRED_INV = 25, + KNOT_EDNS_EDE_TOO_EARLY = 26, + KNOT_EDNS_EDE_NSEC3_ITERS = 27, + KNOT_EDNS_EDE_NONCONF_POLICY = 28, + KNOT_EDNS_EDE_SYNTHESIZED = 29, +} knot_edns_ede_t; + +/*! + * \brief DNS packet section identifiers. + */ +typedef enum { + KNOT_ANSWER = 0, + KNOT_AUTHORITY = 1, + KNOT_ADDITIONAL = 2 +} knot_section_t; + +/*! + * \brief Service Binding (SVCB) Parameter Registry + * + * https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-05 // FIXME + */ +typedef enum { + KNOT_SVCB_PARAM_MANDATORY = 0, + KNOT_SVCB_PARAM_ALPN = 1, + KNOT_SVCB_PARAM_NDALPN = 2, + KNOT_SVCB_PARAM_PORT = 3, + KNOT_SVCB_PARAM_IPV4HINT = 4, + KNOT_SVCB_PARAM_ECH = 5, + KNOT_SVCB_PARAM_IPV6HINT = 6, +} knot_svcb_param_t; + +/*! @} */ diff --git a/src/libknot/control/control.c b/src/libknot/control/control.c new file mode 100644 index 0000000..8656057 --- /dev/null +++ b/src/libknot/control/control.c @@ -0,0 +1,568 @@ +/* Copyright (C) 2023 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 (30 * 1000) + +/*! Accept poll timeout in milliseconds. */ +#define ACCEPT_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. + mode_t mode = S_IWUSR | S_IWGRP; + ctx->listen_sock = net_bound_socket(SOCK_STREAM, &addr, 0, mode); + 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, ACCEPT_TIMEOUT); + if (ret <= 0) { + return (ret == 0) ? KNOT_ETIMEOUT : 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, false); + 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..1d3dcd1 --- /dev/null +++ b/src/libknot/control/control.h @@ -0,0 +1,161 @@ +/* Copyright (C) 2022 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, /*!< Configuration 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 30 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. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_ETIMEOUT if no connection accepted during ACCEPT_TIMEOUT interval. + * \retval KNOT_E* if error. + */ +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..b5eae09 --- /dev/null +++ b/src/libknot/db/db_lmdb.c @@ -0,0 +1,578 @@ +/* Copyright (C) 2022 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/files.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); +} + +/*! \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 = make_dir(opts->path, LMDB_DIR_MODE, true); + 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_READERS_FULL) { + int cleared = 0; + ret = mdb_reader_check(env->env, &cleared); + if (ret == MDB_SUCCESS) { + 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 }; + + int ret = mdb_del(txn->txn, env->dbi, &db_key, NULL); + 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..1bdba8b --- /dev/null +++ b/src/libknot/db/db_trie.c @@ -0,0 +1,176 @@ +/* Copyright (C) 2021 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/mempattern.h" + +static int init(knot_db_t **db, knot_mm_t *mm, void *arg) +{ + if (db == NULL || arg == NULL) { + return KNOT_EINVAL; + } + + _unused_ struct knot_db_trie_opts *opts = arg; + 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..5ff2714 --- /dev/null +++ b/src/libknot/descriptor.c @@ -0,0 +1,425 @@ +/* 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 || + type == KNOT_RRTYPE_SVCB || + type == KNOT_RRTYPE_HTTPS; +} + +_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..14cd3b3 --- /dev/null +++ b/src/libknot/descriptor.h @@ -0,0 +1,307 @@ +/* Copyright (C) 2021 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 resource record descriptions. + * + * \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 according 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..31b8a5f --- /dev/null +++ b/src/libknot/dname.c @@ -0,0 +1,801 @@ +/* Copyright (C) 2022 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 uncompressed 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_ +void knot_dname_copy_lower(knot_dname_t *dst, const knot_dname_t *name) +{ + if (dst == NULL || name == NULL) { + return; + } + + while (*name != '\0') { + uint8_t len = *name; + *dst = len; + for (uint8_t i = 1; i <= len; ++i) { + dst[i] = knot_tolower(name[i]); + } + dst += 1 + len; + name += 1 + len; + } + *dst = '\0'; +} + +_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; + } + + uint8_t *dst = storage + KNOT_DNAME_MAXLEN - 1; + + while (*src != 0) { + uint8_t len = *src++; + *dst = '\0'; + dst -= len; + assert(dst >= storage); + + if (len == 1) { + *dst-- = *src++; + } else { + memcpy(dst--, src, len); + src += len; + } + } + + *dst = storage + KNOT_DNAME_MAXLEN - 1 - dst; + assert(dst >= storage); + + return dst; +} + +_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..5733de9 --- /dev/null +++ b/src/libknot/dname.h @@ -0,0 +1,354 @@ +/* Copyright (C) 2022 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 domain name to lowercase. + * + * \param name Domain name to be converted. + */ +void knot_dname_to_lower(knot_dname_t *name); + +/*! + * \brief Copy lowercased domain name. + * + * \note The output buffer isn't checked if it's big enough! + * + * \param dst Destination buffer. + * \param name Source domain name to be converted. + */ +void knot_dname_copy_lower(knot_dname_t *dst, const 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 remaining 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/dynarray.h b/src/libknot/dynarray.h new file mode 100644 index 0000000..7ea66f9 --- /dev/null +++ b/src/libknot/dynarray.h @@ -0,0 +1,188 @@ +/* Copyright (C) 2022 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/>. + */ + +/*! + * \brief Simple write-once allocation-optimal dynamic array. + * + * Include it into your .c file + * + * prefix - identifier prefix, e.g. ptr -> struct ptr_dynarray, ptr_dynarray_add(), ... + * ntype - data type to be stored. Let it be a number, pointer or small struct + * initial_capacity - how many data items will be allocated on stac and copied with assignment + * + * prefix_dynarray_add() - add a data item + * prefix_dynarray_fix() - call EVERYTIME the array is copied from some already invalid stack + * prefix_dynarray_free() - call EVERYTIME you dismiss all copies of the array + * + */ + +#include <stdlib.h> +#include <assert.h> + +#include "libknot/attribute.h" + +#pragma once + +#define DYNARRAY_VISIBILITY_NORMAL +#define DYNARRAY_VISIBILITY_STATIC static +#define DYNARRAY_VISIBILITY_PUBLIC _public_ + +#define knot_dynarray_declare(prefix, ntype, visibility, initial_capacity) \ + typedef struct prefix ## _dynarray { \ + ssize_t capacity; \ + ssize_t size; \ + ntype *(*arr)(struct prefix ## _dynarray *dynarray); \ + ntype init[initial_capacity]; \ + ntype *_arr; \ + } prefix ## _dynarray_t; \ + \ + visibility ntype *prefix ## _dynarray_arr(prefix ## _dynarray_t *dynarray); \ + visibility ntype *prefix ## _dynarray_add(prefix ## _dynarray_t *dynarray, \ + ntype const *to_add); \ + visibility void prefix ## _dynarray_remove(prefix ## _dynarray_t *dynarray, \ + ntype const *to_remove); \ + visibility void prefix ## _dynarray_sort(prefix ## _dynarray_t *dynarray); \ + visibility ntype *prefix ## _dynarray_bsearch(prefix ## _dynarray_t *dynarray, \ + const ntype *bskey); \ + visibility void prefix ## _dynarray_sort_dedup(prefix ## _dynarray_t *dynarray); \ + visibility void prefix ## _dynarray_free(prefix ## _dynarray_t *dynarray); + +#define knot_dynarray_foreach(prefix, ntype, ptr, array) \ + for (ntype *ptr = prefix ## _dynarray_arr(&(array)); \ + ptr < prefix ## _dynarray_arr(&(array)) + (array).size; ptr++) + +#define knot_dynarray_define(prefix, ntype, visibility) \ + \ + static void prefix ## _dynarray_free__(struct prefix ## _dynarray *dynarray) \ + { \ + if (dynarray->capacity > sizeof(dynarray->init) / sizeof(*dynarray->init)) { \ + free(dynarray->_arr); \ + } \ + } \ + \ + _unused_ \ + visibility ntype *prefix ## _dynarray_arr(struct prefix ## _dynarray *dynarray) \ + { \ + assert(dynarray->size <= dynarray->capacity); \ + return (dynarray->capacity <= sizeof(dynarray->init) / sizeof(*dynarray->init) ? \ + dynarray->init : dynarray->_arr); \ + } \ + \ + static ntype *prefix ## _dynarray_arr_init__(struct prefix ## _dynarray *dynarray) \ + { \ + assert(dynarray->capacity == sizeof(dynarray->init) / sizeof(*dynarray->init)); \ + return dynarray->init; \ + } \ + \ + static ntype *prefix ## _dynarray_arr_arr__(struct prefix ## _dynarray *dynarray) \ + { \ + assert(dynarray->capacity > sizeof(dynarray->init) / sizeof(*dynarray->init)); \ + return dynarray->_arr; \ + } \ + \ + _unused_ \ + visibility ntype *prefix ## _dynarray_add(struct prefix ## _dynarray *dynarray, \ + ntype const *to_add) \ + { \ + if (dynarray->capacity < 0) { \ + return NULL; \ + } \ + if (dynarray->capacity == 0) { \ + dynarray->capacity = sizeof(dynarray->init) / sizeof(*dynarray->init); \ + dynarray->arr = prefix ## _dynarray_arr_init__; \ + } \ + if (dynarray->size >= dynarray->capacity) { \ + ssize_t new_capacity = dynarray->capacity * 2 + 1; \ + ntype *new_arr = calloc(new_capacity, sizeof(ntype)); \ + if (new_arr == NULL) { \ + prefix ## _dynarray_free__(dynarray); \ + dynarray->capacity = dynarray->size = -1; \ + return NULL; \ + } \ + if (dynarray->capacity > 0) { \ + memcpy(new_arr, prefix ## _dynarray_arr(dynarray), \ + dynarray->capacity * sizeof(ntype)); \ + } \ + prefix ## _dynarray_free__(dynarray); \ + dynarray->_arr = new_arr; \ + dynarray->capacity = new_capacity; \ + dynarray->arr = prefix ## _dynarray_arr_arr__; \ + } \ + ntype *add_to = &prefix ## _dynarray_arr(dynarray)[dynarray->size++]; \ + *add_to = *to_add; \ + return add_to; \ + } \ + \ + _unused_ \ + visibility void prefix ## _dynarray_remove(struct prefix ## _dynarray *dynarray, \ + ntype const *to_remove) \ + { \ + ntype *orig_arr = prefix ## _dynarray_arr(dynarray); \ + knot_dynarray_foreach(prefix, ntype, removable, *dynarray) { \ + if (memcmp(removable, to_remove, sizeof(*to_remove)) == 0) { \ + if (removable != orig_arr + --dynarray->size) { \ + *(removable--) = orig_arr[dynarray->size]; \ + } \ + } \ + } /* TODO enable lowering capacity, take care of capacity going back to initial! */ \ + } \ + \ + static int prefix ## _dynarray_memb_cmp(const void *a, const void *b) \ + { \ + return memcmp(a, b, sizeof(ntype)); \ + } \ + \ + _unused_ \ + visibility void prefix ## _dynarray_sort(struct prefix ## _dynarray *dynarray) \ + { \ + ntype *arr = prefix ## _dynarray_arr(dynarray); \ + qsort(arr, dynarray->size, sizeof(*arr), prefix ## _dynarray_memb_cmp); \ + } \ + \ + _unused_ \ + visibility ntype *prefix ## _dynarray_bsearch(struct prefix ## _dynarray *dynarray, const ntype *bskey) \ + { \ + ntype *arr = prefix ## _dynarray_arr(dynarray); \ + return bsearch(bskey, arr, dynarray->size, sizeof(*arr), prefix ## _dynarray_memb_cmp); \ + } \ + \ + _unused_ \ + visibility void prefix ## _dynarray_sort_dedup(struct prefix ## _dynarray *dynarray) \ + { \ + if (dynarray->size > 1) { \ + prefix ## _dynarray_sort(dynarray); \ + ntype *arr = prefix ## _dynarray_arr(dynarray); \ + ntype *rd = arr + 1; \ + ntype *wr = arr + 1; \ + ntype *end = arr + dynarray->size; \ + while (rd != end) { \ + if (memcmp(rd - 1, rd, sizeof(*rd)) != 0) { \ + if (wr != rd) { \ + *wr = *rd; \ + } \ + wr++; \ + } \ + rd++; \ + } \ + dynarray->size = wr - arr; \ + } \ + } \ + _unused_ \ + visibility void prefix ## _dynarray_free(struct prefix ## _dynarray *dynarray) \ + { \ + prefix ## _dynarray_free__(dynarray); \ + memset(dynarray, 0, sizeof(*dynarray)); \ + } diff --git a/src/libknot/endian.h b/src/libknot/endian.h new file mode 100644 index 0000000..037ed68 --- /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) || defined(__CYGWIN__) +# 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..648ca66 --- /dev/null +++ b/src/libknot/errcode.h @@ -0,0 +1,272 @@ +/* Copyright (C) 2022 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_ENOTDIR = -ENOTDIR, + + 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_ENOTTL, + 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_EXPARAM, + KNOT_EEMPTYZONE, + KNOT_ENODB, + KNOT_EUNREACH, + + 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_ENONSEC, + 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..352acd3 --- /dev/null +++ b/src/libknot/error.c @@ -0,0 +1,239 @@ +/* Copyright (C) 2022 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_ENOTDIR, "not a directory" }, + + { 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 prerequisite not met" }, + { KNOT_ETTL, "TTL mismatch" }, + { KNOT_ENOTTL, "no TTL specified" }, + { 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_EXPARAM, "parameter conflict" }, + { KNOT_EEMPTYZONE, "zone is empty" }, + { KNOT_ENODB, "database does not exist" }, + { KNOT_EUNREACH, "remote known to be unreachable" }, + + { 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_ENONSEC, "missing NSEC(3) record" }, + { KNOT_DNSSEC_ENSEC_BITMAP, "wrong NSEC(3) 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..f62c2c7 --- /dev/null +++ b/src/libknot/libknot.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2021 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/attribute.h" +#include "libknot/cookies.h" +#include "libknot/codes.h" +#include "libknot/consts.h" +#include "libknot/descriptor.h" +#include "libknot/dname.h" +#include "libknot/dynarray.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/probe/data.h" +#include "libknot/probe/probe.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/svcb.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/rrtype/zonemd.h" +#include "libknot/wire.h" + +/*! @} */ 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..728bb3e --- /dev/null +++ b/src/libknot/packet/pkt.c @@ -0,0 +1,837 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "libknot/attribute.h" +#include "libknot/packet/pkt.h" +#include "libknot/codes.h" +#include "libknot/descriptor.h" +#include "libknot/errcode.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/tsig-op.h" +#include "libknot/packet/wire.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/wire.h" +#include "contrib/mempattern.h" +#include "contrib/wire_ctx.h" + +/*! \brief Packet RR array growth step. */ +#define NEXT_RR_ALIGN 16 +#define NEXT_RR_COUNT(count) (((count) / NEXT_RR_ALIGN + 1) * NEXT_RR_ALIGN) + +/*! \brief Scan packet for RRSet existence. */ +static bool pkt_contains(const knot_pkt_t *packet, const knot_rrset_t *rrset) +{ + assert(packet); + assert(rrset); + + for (int i = 0; i < packet->rrset_count; ++i) { + const uint16_t type = packet->rr[i].type; + const knot_rdata_t *data = packet->rr[i].rrs.rdata; + if (type == rrset->type && data == rrset->rrs.rdata) { + return true; + } + } + + return false; +} + +/*! \brief Free all RRSets and reset RRSet count. */ +static void pkt_free_data(knot_pkt_t *pkt) +{ + assert(pkt); + + /* Free RRSets if applicable. */ + for (uint16_t i = 0; i < pkt->rrset_count; ++i) { + if (pkt->rr_info[i].flags & KNOT_PF_FREE) { + knot_rrset_clear(&pkt->rr[i], &pkt->mm); + } + } + pkt->rrset_count = 0; + + /* Free EDNS option positions. */ + mm_free(&pkt->mm, pkt->edns_opts); + pkt->edns_opts = 0; +} + +/*! \brief Allocate new wireformat of given length, assuming *pkt is zeroed. */ +static int pkt_wire_alloc(knot_pkt_t *pkt, uint16_t len) +{ + assert(pkt); + + if (len < KNOT_WIRE_HEADER_SIZE) { + return KNOT_ERANGE; + } + + pkt->wire = mm_alloc(&pkt->mm, len); + if (pkt->wire == NULL) { + return KNOT_ENOMEM; + } + + pkt->flags |= KNOT_PF_FREE; + pkt->max_size = len; + + /* Reset to header size. */ + pkt->size = KNOT_WIRE_HEADER_SIZE; + memset(pkt->wire, 0, pkt->size); + + return KNOT_EOK; +} + +/*! \brief Set packet wireformat to an existing memory. */ +static void pkt_wire_set(knot_pkt_t *pkt, void *wire, uint16_t len) +{ + assert(pkt); + + pkt->wire = wire; + pkt->size = pkt->max_size = len; + pkt->parsed = 0; +} + +/*! \brief Calculate remaining size in the packet. */ +static uint16_t pkt_remaining(knot_pkt_t *pkt) +{ + assert(pkt); + + return pkt->max_size - pkt->size - pkt->reserved; +} + +/*! \brief Return RR count for given section (from wire xxCOUNT in header). */ +static uint16_t pkt_rr_wirecount(knot_pkt_t *pkt, knot_section_t section_id) +{ + assert(pkt); + switch (section_id) { + case KNOT_ANSWER: return knot_wire_get_ancount(pkt->wire); + case KNOT_AUTHORITY: return knot_wire_get_nscount(pkt->wire); + case KNOT_ADDITIONAL: return knot_wire_get_arcount(pkt->wire); + default: assert(0); return 0; + } +} + +/*! \brief Update RR count for given section (wire xxCOUNT in header). */ +static void pkt_rr_wirecount_add(knot_pkt_t *pkt, knot_section_t section_id, + int16_t val) +{ + assert(pkt); + switch (section_id) { + case KNOT_ANSWER: knot_wire_add_ancount(pkt->wire, val); break; + case KNOT_AUTHORITY: knot_wire_add_nscount(pkt->wire, val); break; + case KNOT_ADDITIONAL: knot_wire_add_arcount(pkt->wire, val); break; + } +} + +/*! \brief Reserve enough space in the RR arrays. */ +static int pkt_rr_array_alloc(knot_pkt_t *pkt, uint16_t count) +{ + /* Enough space. */ + if (pkt->rrset_allocd >= count) { + return KNOT_EOK; + } + + /* Allocate rr_info and rr fields to next size. */ + size_t next_size = NEXT_RR_COUNT(count); + knot_rrinfo_t *rr_info = mm_alloc(&pkt->mm, sizeof(knot_rrinfo_t) * next_size); + if (rr_info == NULL) { + return KNOT_ENOMEM; + } + + knot_rrset_t *rr = mm_alloc(&pkt->mm, sizeof(knot_rrset_t) * next_size); + if (rr == NULL) { + mm_free(&pkt->mm, rr_info); + return KNOT_ENOMEM; + } + + /* Copy and free the old data, if any. */ + if (pkt->rrset_allocd > 0) { + memcpy(rr_info, pkt->rr_info, pkt->rrset_allocd * sizeof(knot_rrinfo_t)); + memcpy(rr, pkt->rr, pkt->rrset_allocd * sizeof(knot_rrset_t)); + mm_free(&pkt->mm, pkt->rr); + mm_free(&pkt->mm, pkt->rr_info); + } + pkt->rr = rr; + pkt->rr_info = rr_info; + pkt->rrset_allocd = next_size; + + return KNOT_EOK; +} + +static void compr_clear(knot_compr_t *compr) +{ + compr->rrinfo = NULL; + compr->suffix.pos = 0; + compr->suffix.labels = 0; +} + +/*! \brief Clear the packet and switch wireformat pointers (possibly allocate new). */ +static int pkt_init(knot_pkt_t *pkt, void *wire, uint16_t len, knot_mm_t *mm) +{ + assert(pkt); + + memset(pkt, 0, offsetof(knot_pkt_t, lower_qname)); + pkt->lower_qname[0] = '\0'; + + /* No data to free, set memory context. */ + memcpy(&pkt->mm, mm, sizeof(knot_mm_t)); + + /* Initialize wire. */ + int ret = KNOT_EOK; + if (wire == NULL) { + ret = pkt_wire_alloc(pkt, len); + } else { + pkt_wire_set(pkt, wire, len); + } + + /* Initialize compression context (zeroed above). */ + pkt->compr.wire = pkt->wire; + + return ret; +} + +/*! \brief Reset packet parse state. */ +static void sections_reset(knot_pkt_t *pkt) +{ + pkt->current = KNOT_ANSWER; + memset(pkt->sections, 0, sizeof(pkt->sections)); + (void)knot_pkt_begin(pkt, KNOT_ANSWER); +} + +/*! \brief Allocate new packet using memory context. */ +static knot_pkt_t *pkt_new_mm(void *wire, uint16_t len, knot_mm_t *mm) +{ + assert(mm); + + knot_pkt_t *pkt = mm_alloc(mm, sizeof(knot_pkt_t)); + if (pkt == NULL) { + return NULL; + } + + if (pkt_init(pkt, wire, len, mm) != KNOT_EOK) { + mm_free(mm, pkt); + return NULL; + } + + return pkt; +} + +_public_ +knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm) +{ + /* Default memory allocator if NULL. */ + knot_mm_t _mm; + if (mm == NULL) { + mm_ctx_init(&_mm); + mm = &_mm; + } + + return pkt_new_mm(wire, len, mm); +} + +static int append_tsig(knot_pkt_t *dst, const knot_pkt_t *src) +{ + /* Check if a wire TSIG is available. */ + if (src->tsig_wire.pos != NULL) { + if (dst->max_size < src->size + src->tsig_wire.len) { + return KNOT_ESPACE; + } + memcpy(dst->wire + dst->size, src->tsig_wire.pos, + src->tsig_wire.len); + dst->size += src->tsig_wire.len; + + /* Increment arcount. */ + knot_wire_set_arcount(dst->wire, + knot_wire_get_arcount(dst->wire) + 1); + } else { + return knot_tsig_append(dst->wire, &dst->size, dst->max_size, + src->tsig_rr); + } + + return KNOT_EOK; +} + +_public_ +int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + if (dst->max_size < src->size) { + return KNOT_ESPACE; + } + memcpy(dst->wire, src->wire, src->size); + dst->size = src->size; + + /* Append TSIG record. */ + if (src->tsig_rr) { + int ret = append_tsig(dst, src); + if (ret != KNOT_EOK) { + return ret; + } + } + + /* Invalidate arrays. */ + dst->rr = NULL; + dst->rr_info = NULL; + dst->rrset_count = 0; + dst->rrset_allocd = 0; + + /* @note This could be done more effectively if needed. */ + return knot_pkt_parse(dst, 0); +} + +static void payload_clear(knot_pkt_t *pkt) +{ + assert(pkt); + + /* Keep question. */ + pkt->parsed = 0; + pkt->reserved = 0; + + /* Free RRSets if applicable. */ + pkt_free_data(pkt); + + /* Reset sections. */ + sections_reset(pkt); + + /* Reset special types. */ + pkt->opt_rr = NULL; + pkt->tsig_rr = NULL; + + /* Reset TSIG wire reference. */ + pkt->tsig_wire.pos = NULL; + pkt->tsig_wire.len = 0; +} + +_public_ +int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query) +{ + if (pkt == NULL || query == NULL) { + return KNOT_EINVAL; + } + + /* Header + question size. */ + size_t base_size = KNOT_WIRE_HEADER_SIZE + knot_pkt_question_size(query); + if (base_size > pkt->max_size) { + return KNOT_ESPACE; + } + + pkt->size = base_size; + memcpy(pkt->wire, query->wire, base_size); + + /* Copy lowercased QNAME. */ + pkt->qname_size = query->qname_size; + if (query->qname_size == 0) { + /* Reset question count if malformed. */ + knot_wire_set_qdcount(pkt->wire, 0); + } + memcpy(pkt->lower_qname, query->lower_qname, pkt->qname_size); + + /* Update flags and section counters. */ + knot_wire_set_ancount(pkt->wire, 0); + knot_wire_set_nscount(pkt->wire, 0); + knot_wire_set_arcount(pkt->wire, 0); + + knot_wire_set_qr(pkt->wire); + knot_wire_clear_tc(pkt->wire); + knot_wire_clear_ad(pkt->wire); + knot_wire_clear_ra(pkt->wire); + knot_wire_clear_aa(pkt->wire); + knot_wire_clear_z(pkt->wire); + + /* Clear payload. */ + payload_clear(pkt); + + /* Clear compression context. */ + compr_clear(&pkt->compr); + + return KNOT_EOK; +} + +_public_ +void knot_pkt_clear(knot_pkt_t *pkt) +{ + if (pkt == NULL) { + return; + } + + /* Reset to header size. */ + pkt->size = KNOT_WIRE_HEADER_SIZE; + memset(pkt->wire, 0, pkt->size); + + /* Clear payload. */ + payload_clear(pkt); + + /* Clear compression context. */ + compr_clear(&pkt->compr); + + /* Initialize lowercased QNAME. */ + pkt->lower_qname[0] = '\0'; +} + +_public_ +void knot_pkt_free(knot_pkt_t *pkt) +{ + if (pkt == NULL) { + return; + } + + /* Free temporary RRSets. */ + pkt_free_data(pkt); + + /* Free RR/RR info arrays. */ + mm_free(&pkt->mm, pkt->rr); + mm_free(&pkt->mm, pkt->rr_info); + + /* Free the space for wireformat. */ + if (pkt->flags & KNOT_PF_FREE) { + mm_free(&pkt->mm, pkt->wire); + } + + mm_free(&pkt->mm, pkt); +} + +_public_ +int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size) +{ + if (pkt == NULL) { + return KNOT_EINVAL; + } + + /* Reserve extra space (if possible). */ + if (pkt_remaining(pkt) >= size) { + pkt->reserved += size; + return KNOT_EOK; + } else { + return KNOT_ERANGE; + } +} + +_public_ +int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size) +{ + if (pkt == NULL) { + return KNOT_EINVAL; + } + + if (pkt->reserved >= size) { + pkt->reserved -= size; + return KNOT_EOK; + } else { + return KNOT_ERANGE; + } +} + +_public_ +int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id) +{ + if (pkt == NULL || section_id < pkt->current) { + return KNOT_EINVAL; + } + + /* Remember watermark but not on repeated calls. */ + pkt->sections[section_id].pkt = pkt; + if (section_id > pkt->current) { + pkt->sections[section_id].pos = pkt->rrset_count; + } + + pkt->current = section_id; + + return KNOT_EOK; +} + +_public_ +int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype) +{ + if (pkt == NULL || qname == NULL) { + return KNOT_EINVAL; + } + + assert(pkt->size == KNOT_WIRE_HEADER_SIZE); + assert(pkt->rrset_count == 0); + + /* Copy name into wire format buffer. */ + wire_ctx_t wire = wire_ctx_init(pkt->wire, pkt->max_size); + wire_ctx_set_offset(&wire, KNOT_WIRE_HEADER_SIZE); + + int qname_len = knot_dname_to_wire(wire.position, + qname, wire_ctx_available(&wire)); + if (qname_len < 0) { + return qname_len; + } + wire_ctx_skip(&wire, qname_len); + + /* Copy QNAME and canonicalize to lowercase. */ + knot_dname_copy_lower(pkt->lower_qname, qname); + + /* Copy QTYPE & QCLASS */ + wire_ctx_write_u16(&wire, qtype); + wire_ctx_write_u16(&wire, qclass); + + /* Check errors. */ + if (wire.error != KNOT_EOK) { + return wire.error; + } + + /* Update question count and sizes. */ + knot_wire_set_qdcount(pkt->wire, 1); + pkt->size = wire_ctx_offset(&wire); + pkt->qname_size = qname_len; + + /* Start writing ANSWER. */ + return knot_pkt_begin(pkt, KNOT_ANSWER); +} + +_public_ +int knot_pkt_put_rotate(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, + uint16_t rotate, uint16_t flags) +{ + if (pkt == NULL || rr == NULL) { + return KNOT_EINVAL; + } + + /* Reserve memory for RR descriptors. */ + int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1); + if (ret != KNOT_EOK) { + return ret; + } + + /* Check for double insertion. */ + if ((flags & KNOT_PF_CHECKDUP) && pkt_contains(pkt, rr)) { + return KNOT_EOK; + } + + knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count]; + memset(rrinfo, 0, sizeof(knot_rrinfo_t)); + rrinfo->pos = pkt->size; + rrinfo->flags = flags; + rrinfo->compress_ptr[0] = compr_hint; + memcpy(pkt->rr + pkt->rrset_count, rr, sizeof(knot_rrset_t)); + + /* Disable compression if no QNAME is available. */ + knot_compr_t *compr = NULL; + if (knot_pkt_qname(pkt) != NULL) { + /* Initialize compression context if it did not happen yet. */ + pkt->compr.rrinfo = rrinfo; + if (pkt->compr.suffix.pos == 0) { + pkt->compr.suffix.pos = KNOT_WIRE_HEADER_SIZE; + pkt->compr.suffix.labels = + knot_dname_labels(pkt->compr.wire + pkt->compr.suffix.pos, + pkt->compr.wire); + } + + compr = &pkt->compr; + } + + uint8_t *pos = pkt->wire + pkt->size; + size_t maxlen = pkt_remaining(pkt); + + /* Write RRSet to wireformat. */ + ret = knot_rrset_to_wire_extra(rr, pos, maxlen, rotate, compr, flags); + if (ret < 0) { + /* Truncate packet if required. */ + if (ret == KNOT_ESPACE && !(flags & KNOT_PF_NOTRUNC)) { + knot_wire_set_tc(pkt->wire); + } + return ret; + } + + size_t len = ret; + uint16_t rr_added = rr->rrs.count; + + /* Keep reference to special types. */ + if (rr->type == KNOT_RRTYPE_OPT) { + pkt->opt_rr = &pkt->rr[pkt->rrset_count]; + } + + if (rr_added > 0) { + pkt->rrset_count += 1; + pkt->sections[pkt->current].count += 1; + pkt->size += len; + pkt_rr_wirecount_add(pkt, pkt->current, rr_added); + } + + return KNOT_EOK; +} + +_public_ +int knot_pkt_parse_question(knot_pkt_t *pkt) +{ + if (pkt == NULL) { + return KNOT_EINVAL; + } + + /* Check at least header size. */ + if (pkt->size < KNOT_WIRE_HEADER_SIZE) { + return KNOT_EMALF; + } + + /* We have at least some DNS header. */ + pkt->parsed = KNOT_WIRE_HEADER_SIZE; + + /* Check QD count. */ + uint16_t qd = knot_wire_get_qdcount(pkt->wire); + if (qd > 1) { + return KNOT_EMALF; + } + + /* No question. */ + if (qd == 0) { + pkt->qname_size = 0; + return KNOT_EOK; + } + + /* Process question. */ + int len = knot_dname_wire_check(pkt->wire + pkt->parsed, + pkt->wire + pkt->size, + NULL /* No compression in QNAME. */); + if (len <= 0) { + return KNOT_EMALF; + } + + /* Check QCLASS/QTYPE size. */ + uint16_t question_size = len + 2 * sizeof(uint16_t); /* QCLASS + QTYPE */ + if (pkt->parsed + question_size > pkt->size) { + return KNOT_EMALF; + } + + pkt->parsed += question_size; + pkt->qname_size = len; + + /* Copy QNAME and canonicalize to lowercase. */ + knot_dname_copy_lower(pkt->lower_qname, pkt->wire + KNOT_WIRE_HEADER_SIZE); + + return KNOT_EOK; +} + +/*! \brief Check constraints (position, uniqueness, validity) for special types + * (TSIG, OPT). + */ +static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_size, + unsigned flags) +{ + switch (rr->type) { + case KNOT_RRTYPE_TSIG: + if (pkt->current != KNOT_ADDITIONAL || pkt->tsig_rr != NULL || + !knot_tsig_rdata_is_ok(rr)) { + return KNOT_EMALF; + } + + /* Strip TSIG RR from wireformat and decrease ARCOUNT. */ + if (!(flags & KNOT_PF_KEEPWIRE)) { + pkt->parsed -= rr_size; + pkt->size -= rr_size; + pkt->tsig_wire.pos = pkt->wire + pkt->parsed; + pkt->tsig_wire.len = rr_size; + knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1); + } + + pkt->tsig_rr = rr; + break; + case KNOT_RRTYPE_OPT: + if (pkt->current != KNOT_ADDITIONAL || pkt->opt_rr != NULL || + knot_edns_get_options(rr, &pkt->edns_opts, &pkt->mm) != KNOT_EOK) { + return KNOT_EMALF; + } + + pkt->opt_rr = rr; + break; + default: + break; + } + + return KNOT_EOK; +} + +static int parse_rr(knot_pkt_t *pkt, unsigned flags) +{ + assert(pkt); + + if (pkt->parsed >= pkt->size) { + return KNOT_EFEWDATA; + } + + /* Reserve memory for RR descriptors. */ + int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1); + if (ret != KNOT_EOK) { + return ret; + } + + /* Initialize RR info. */ + memset(&pkt->rr_info[pkt->rrset_count], 0, sizeof(knot_rrinfo_t)); + pkt->rr_info[pkt->rrset_count].pos = pkt->parsed; + pkt->rr_info[pkt->rrset_count].flags = KNOT_PF_FREE; + + /* Parse wire format. */ + size_t rr_size = pkt->parsed; + knot_rrset_t *rr = &pkt->rr[pkt->rrset_count]; + ret = knot_rrset_rr_from_wire(pkt->wire, &pkt->parsed, pkt->size, + rr, &pkt->mm, !(flags & KNOT_PF_NOCANON)); + if (ret != KNOT_EOK) { + return ret; + } + + /* Calculate parsed RR size from before/after parsing. */ + rr_size = (pkt->parsed - rr_size); + + /* Update packet RRSet count. */ + ++pkt->rrset_count; + ++pkt->sections[pkt->current].count; + + /* Check special RRs (OPT and TSIG). */ + return check_rr_constraints(pkt, rr, rr_size, flags); +} + +static int parse_section(knot_pkt_t *pkt, unsigned flags) +{ + assert(pkt); + + uint16_t rr_parsed = 0; + uint16_t rr_count = pkt_rr_wirecount(pkt, pkt->current); + + /* Parse all RRs belonging to the section. */ + for (rr_parsed = 0; rr_parsed < rr_count; ++rr_parsed) { + int ret = parse_rr(pkt, flags); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +static int parse_payload(knot_pkt_t *pkt, unsigned flags) +{ + assert(pkt); + assert(pkt->wire); + assert(pkt->size > 0); + + /* Reserve memory in advance to avoid resizing. */ + size_t rr_count = knot_wire_get_ancount(pkt->wire) + + knot_wire_get_nscount(pkt->wire) + + knot_wire_get_arcount(pkt->wire); + + if (rr_count > pkt->size / KNOT_WIRE_RR_MIN_SIZE) { + return KNOT_EMALF; + } + + int ret = pkt_rr_array_alloc(pkt, rr_count); + if (ret != KNOT_EOK) { + return ret; + } + + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + ret = knot_pkt_begin(pkt, i); + if (ret != KNOT_EOK) { + return ret; + } + ret = parse_section(pkt, flags); + if (ret != KNOT_EOK) { + return ret; + } + } + + /* TSIG must be last record of AR if present. */ + const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL); + if (pkt->tsig_rr != NULL) { + const knot_rrset_t *last_rr = knot_pkt_rr(ar, ar->count - 1); + if (ar->count > 0 && pkt->tsig_rr->rrs.rdata != last_rr->rrs.rdata) { + return KNOT_EMALF; + } + } + + /* Check for trailing garbage. */ + if (pkt->parsed < pkt->size) { + return KNOT_ETRAIL; + } + + return KNOT_EOK; +} + +_public_ +int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags) +{ + if (pkt == NULL) { + return KNOT_EINVAL; + } + + /* Reset parse state. */ + sections_reset(pkt); + + int ret = knot_pkt_parse_question(pkt); + if (ret == KNOT_EOK) { + ret = parse_payload(pkt, flags); + } + + return ret; +} + +_public_ +uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt) +{ + if (pkt == NULL) { + return 0; + } + + /* Get header RCODE. */ + uint16_t rcode = knot_wire_get_rcode(pkt->wire); + + /* Update to extended RCODE if EDNS is available. */ + if (pkt->opt_rr != NULL) { + uint8_t opt_rcode = knot_edns_get_ext_rcode(pkt->opt_rr); + rcode = knot_edns_whole_rcode(opt_rcode, rcode); + } + + /* Return if not NOTAUTH. */ + if (rcode != KNOT_RCODE_NOTAUTH) { + return rcode; + } + + /* Get TSIG RCODE. */ + uint16_t tsig_rcode = KNOT_RCODE_NOERROR; + if (pkt->tsig_rr != NULL) { + tsig_rcode = knot_tsig_rdata_error(pkt->tsig_rr); + } + + /* Return proper RCODE. */ + if (tsig_rcode != KNOT_RCODE_NOERROR) { + return tsig_rcode; + } else { + return rcode; + } +} + +_public_ +const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt) +{ + if (pkt == NULL) { + return ""; + } + + uint16_t rcode = knot_pkt_ext_rcode(pkt); + + const knot_lookup_t *item = NULL; + if (pkt->tsig_rr != NULL) { + item = knot_lookup_by_id(knot_tsig_rcode_names, rcode); + } + if (item == NULL) { + item = knot_lookup_by_id(knot_rcode_names, rcode); + } + + return (item != NULL) ? item->name : ""; +} diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h new file mode 100644 index 0000000..f5e218f --- /dev/null +++ b/src/libknot/packet/pkt.h @@ -0,0 +1,416 @@ +/* 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. */ + + /*! Lowercased QNAME. MUST BE LAST ITEM! */ + knot_dname_storage_t lower_qname; +}; + +/*! + * \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 const knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return NULL; + } + + return pkt->lower_qname; +} + +static inline const knot_dname_t *knot_pkt_wire_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_DEFAULT); + } +} + +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..2b02969 --- /dev/null +++ b/src/libknot/packet/rrset-wire.c @@ -0,0 +1,727 @@ +/* Copyright (C) 2021 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; + + if (knot_wire_is_pointer(written_at)) { + offset = knot_wire_get_pointer(written_at); + } + + 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 (int 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/probe/data.c b/src/libknot/probe/data.c new file mode 100644 index 0000000..5d831b2 --- /dev/null +++ b/src/libknot/probe/data.c @@ -0,0 +1,135 @@ +/* Copyright (C) 2023 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 <sys/types.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/probe/probe.h" +#include "contrib/macros.h" + +_public_ +int knot_probe_data_set(knot_probe_data_t *data, knot_probe_proto_t proto, + const struct sockaddr_storage *local_addr, + const struct sockaddr_storage *remote_addr, + const knot_pkt_t *query, const knot_pkt_t *reply, + uint16_t rcode) +{ + if (data == NULL || remote_addr == NULL || query == NULL) { + return KNOT_EINVAL; + } + + data->proto = proto; + + if (remote_addr->ss_family == AF_INET) { + const struct sockaddr_in *sa = (struct sockaddr_in *)remote_addr; + const struct sockaddr_in *da = (struct sockaddr_in *)local_addr; + + memcpy(data->remote.addr, &sa->sin_addr, sizeof(sa->sin_addr)); + memset(data->remote.addr + sizeof(sa->sin_addr), 0, + sizeof(data->remote.addr) - sizeof(sa->sin_addr)); + data->remote.port = be16toh(sa->sin_port); + + if (da != NULL) { + memcpy(data->local.addr, &da->sin_addr, sizeof(da->sin_addr)); + memset(data->local.addr + sizeof(da->sin_addr), 0, + sizeof(data->local.addr) - sizeof(da->sin_addr)); + data->local.port = be16toh(da->sin_port); + } else { + memset(&data->local, 0, sizeof(data->local)); + } + + data->ip = 4; + } else if (remote_addr->ss_family == AF_INET6) { + const struct sockaddr_in6 *sa = (struct sockaddr_in6 *)remote_addr; + const struct sockaddr_in6 *da = (struct sockaddr_in6 *)local_addr; + + memcpy(data->remote.addr, &sa->sin6_addr, sizeof(sa->sin6_addr)); + data->remote.port = be16toh(sa->sin6_port); + + if (da != NULL) { + memcpy(data->local.addr, &da->sin6_addr, sizeof(da->sin6_addr)); + data->local.port = be16toh(da->sin6_port); + } else { + memset(&data->local, 0, sizeof(data->local)); + } + + data->ip = 6; + } else { + memset(&data->remote, 0, sizeof(data->remote)); + memset(&data->local, 0, sizeof(data->local)); + + data->ip = 0; + } + + if (reply != NULL) { + memcpy(&data->reply.hdr, reply->wire, sizeof(data->reply.hdr)); + data->reply.size = knot_pkt_size(reply); + data->reply.rcode = rcode; + } else { + memset(&data->reply, 0, sizeof(data->reply)); + } + data->reply.ede = KNOT_PROBE_DATA_EDE_NONE; + + data->tcp_rtt = 0; + + if (query->opt_rr != NULL) { + data->query_edns.options = 0; + data->query_edns.payload = knot_edns_get_payload(query->opt_rr); + data->query_edns.version = knot_edns_get_version(query->opt_rr); + data->query_edns.present = 1; + data->query_edns.flag_do = knot_edns_do(query->opt_rr); + if (query->edns_opts != NULL) { + for (int i = 0; i <= KNOT_EDNS_MAX_OPTION_CODE; i++) { + if (query->edns_opts->ptr[i] != NULL) { + data->query_edns.options |= (1 << i); + } + } + } + data->query_edns.reserved = 0; + } else { + memset(&data->query_edns, 0, sizeof(data->query_edns)); + } + + memcpy(&data->query.hdr, query->wire, sizeof(data->query.hdr)); + data->query.size = knot_pkt_size(query); + data->query.qclass = knot_pkt_qclass(query); + data->query.qtype = knot_pkt_qtype(query); + data->query.qname_len = knot_dname_size(knot_pkt_qname(query)); + memcpy(data->query.qname, knot_pkt_qname(query), data->query.qname_len); + memset(data->query.qname + data->query.qname_len, 0, + MIN(8, sizeof(data->query.qname) - data->query.qname_len)); + + return KNOT_EOK; +} + +_public_ +uint32_t knot_probe_tcp_rtt(int sockfd) +{ +#if defined(__linux__) + struct tcp_info info = { 0 }; + socklen_t info_length = sizeof(info); + if (getsockopt(sockfd, SOL_TCP, TCP_INFO, &info, &info_length) == 0) { + return info.tcpi_rtt; + } +#endif + + return 0; +} diff --git a/src/libknot/probe/data.h b/src/libknot/probe/data.h new file mode 100644 index 0000000..efd5135 --- /dev/null +++ b/src/libknot/probe/data.h @@ -0,0 +1,132 @@ +/* Copyright (C) 2022 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 DNS traffic probe data structure. + * + * \addtogroup probe + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include "libknot/consts.h" +#include "libknot/packet/pkt.h" + +/*! EDE absence indication. */ +#define KNOT_PROBE_DATA_EDE_NONE 0xFFFF + +/*! Data transport protocol types. */ +typedef enum { + KNOT_PROBE_PROTO_UDP = 0, + KNOT_PROBE_PROTO_TCP, + KNOT_PROBE_PROTO_QUIC, + KNOT_PROBE_PROTO_TLS, + KNOT_PROBE_PROTO_HTTPS, +} knot_probe_proto_t; + +/*! DNS message header in wire format (network byte order!). */ +typedef struct { + uint16_t id; + uint8_t byte3; /*!< QR, OPCODE, AA, TC, RD. */ + uint8_t byte4; /*!< RA, Z, AD, CD, RCODE. */ + uint16_t questions; + uint16_t answers; + uint16_t authorities; + uint16_t additionals; +} knot_probe_data_wire_hdr_t; + +/*! Probe data unit. */ +typedef struct { + uint8_t ip; /*!< IP protocol: 4 or 6. */ + uint8_t proto; /*!< Transport protocol \ref knot_probe_proto_t. */ + + struct { + uint8_t addr[16]; /*!< Query destination address. */ + uint16_t port; /*!< Query destination port. */ + } local; + + struct { + uint8_t addr[16]; /*!< Query source address. */ + uint16_t port; /*!< Query source port. */ + } remote; + + struct { + knot_probe_data_wire_hdr_t hdr; /*!< DNS reply header. */ + uint16_t size; /*!< DNS reply size (0 if no reply). */ + uint16_t rcode; /*!< Final RCODE (header + EDNS + TSIG). */ + uint16_t ede; /*!< EDE code if present. */ + } reply; + + uint32_t tcp_rtt; /*!< Average TCP RTT in microseconds. */ + + struct { + uint32_t options; /*!< EDNS options bit map (e.g. NSID ~ 1 << 3). */ + uint16_t payload; /*!< EDNS payload size. */ + uint8_t version; /*!< EDNS version. */ + uint8_t present : 1; /*!< EDNS presence indication. */ + uint8_t flag_do : 1; /*!< DO flag indication. */ + uint8_t reserved : 6; /*!< Unused. */ + } query_edns; + + struct { + knot_probe_data_wire_hdr_t hdr; /*!< DNS query header. */ + uint16_t size; /*!< DNS query size. */ + uint16_t qclass; /*!< QCLASS. */ + uint16_t qtype; /*!< QTYPE. */ + uint8_t qname_len; /*!< QNAME length. */ + uint8_t qname[KNOT_DNAME_MAXLEN]; /*!< QNAME. */ + } query; +} knot_probe_data_t; + +/*! + * \brief Initializes a probe data unit. + * + * \note 'reply.ede' and 'tcp.rtt' are zeroed only and require further setting. + * + * \param data Output probe data unit. + * \param proto Transport protocol \ref knot_probe_proto_t. + * \param local_addr Query destination address (optional). + * \param remote_addr Query source address. + * \param query Query packet. + * \param reply Reply packet (optional). + * \param rcode Extended rcode (combination of RCODE, EDNS, TSIG). + * + * \retval KNOT_EOK Success. + * \return KNOT_E* If error. + */ +int knot_probe_data_set(knot_probe_data_t *data, knot_probe_proto_t proto, + const struct sockaddr_storage *local_addr, + const struct sockaddr_storage *remote_addr, + const knot_pkt_t *query, const knot_pkt_t *reply, + uint16_t rcode); + +/*! + * \brief Gets averate TCP RRT for a given socket descriptor. + * + * \note Implemented on Linux only! + * + * \param sockfd Socket descriptor of a TCP connection. + * + * \return Average TCP RTT in microseconds. + */ +uint32_t knot_probe_tcp_rtt(int sockfd); + +/*! @} */ diff --git a/src/libknot/probe/probe.c b/src/libknot/probe/probe.c new file mode 100644 index 0000000..341c48b --- /dev/null +++ b/src/libknot/probe/probe.c @@ -0,0 +1,228 @@ +/* Copyright (C) 2023 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 <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "libknot/probe/probe.h" +#include "contrib/time.h" + +struct knot_probe { + struct sockaddr_un path; + uint32_t last_unconn_time; + bool consumer; + int fd; +}; + +_public_ +knot_probe_t *knot_probe_alloc(void) +{ + knot_probe_t *probe = calloc(1, sizeof(*probe)); + if (probe == NULL) { + return NULL; + } + + probe->fd = -1; + + return probe; +} + +_public_ +void knot_probe_free(knot_probe_t *probe) +{ + if (probe == NULL) { + return; + } + + close(probe->fd); + if (probe->consumer) { + (void)unlink(probe->path.sun_path); + } + free(probe); +} + +static int probe_connect(knot_probe_t *probe) +{ + return connect(probe->fd, (const struct sockaddr *)(&probe->path), + sizeof(probe->path)); +} + +static int probe_init(knot_probe_t *probe, const char *dir, uint16_t idx) +{ + if (probe == NULL || dir == NULL || idx == 0) { + return KNOT_EINVAL; + } + + probe->path.sun_family = AF_UNIX; + int ret = snprintf(probe->path.sun_path, sizeof(probe->path.sun_path), + "%s/probe%02u.sock", dir, idx); + if (ret < 0 || ret >= sizeof(probe->path.sun_path)) { + return KNOT_ERANGE; + } + + probe->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (probe->fd < 0) { + return knot_map_errno(); + } + + if (fcntl(probe->fd, F_SETFL, O_NONBLOCK) == -1) { + close(probe->fd); + probe->fd = -1; + return knot_map_errno(); + } + + return KNOT_EOK; +} + +_public_ +int knot_probe_set_producer(knot_probe_t *probe, const char *dir, uint16_t idx) +{ + int ret = probe_init(probe, dir, idx); + if (ret != KNOT_EOK) { + return ret; + } + + ret = probe_connect(probe); + if (ret != 0) { + return KNOT_ECONN; + } + + return KNOT_EOK; +} + +_public_ +int knot_probe_set_consumer(knot_probe_t *probe, const char *dir, uint16_t idx) +{ + int ret = probe_init(probe, dir, idx); + if (ret != KNOT_EOK) { + return ret; + } + + probe->consumer = true; + + (void)unlink(probe->path.sun_path); + + ret = bind(probe->fd, (const struct sockaddr *)(&probe->path), + sizeof(probe->path)); + if (ret != 0) { + return knot_map_errno(); + } + + if (chmod(probe->path.sun_path, S_IWUSR | S_IWGRP | S_IWOTH) != 0) { + close(probe->fd); + return knot_map_errno(); + } + + return KNOT_EOK; +} + +_public_ +int knot_probe_fd(knot_probe_t *probe) +{ + if (probe == NULL) { + return -1; + } + + return probe->fd; +} + +_public_ +int knot_probe_produce(knot_probe_t *probe, const knot_probe_data_t *data, uint8_t count) +{ + if (probe == NULL || data == NULL || count != 1) { + return KNOT_EINVAL; + } + + size_t used_len = sizeof(*data) - KNOT_DNAME_MAXLEN + data->query.qname_len; + if (send(probe->fd, data, used_len, 0) == -1) { + struct timespec now = time_now(); + if (now.tv_sec - probe->last_unconn_time > 2) { + probe->last_unconn_time = now.tv_sec; + if ((errno == ENOTCONN || errno == ECONNREFUSED) && + probe_connect(probe) == 0 && + send(probe->fd, data, used_len, 0) > 0) { + return KNOT_EOK; + } + } + return knot_map_errno(); + } + + return KNOT_EOK; +} + +_public_ +int knot_probe_consume(knot_probe_t *probe, knot_probe_data_t *data, uint8_t count, + int timeout_ms) +{ + if (probe == NULL || data == NULL || count == 0) { + return KNOT_EINVAL; + } + +#ifdef ENABLE_RECVMMSG + struct mmsghdr msgs[count]; + struct iovec iovecs[count]; + + memset(msgs, 0, sizeof(msgs)); + for (int i = 0; i < count; i++) { + iovecs[i].iov_base = &(data[i]); + iovecs[i].iov_len = sizeof(*data); + msgs[i].msg_hdr.msg_iov = &iovecs[i]; + msgs[i].msg_hdr.msg_iovlen = 1; + } +#else + struct iovec iov = { + .iov_base = data, + .iov_len = sizeof(*data) + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1 + }; +#endif + + struct pollfd pfd = { .fd = probe->fd, .events = POLLIN }; + int ret = poll(&pfd, 1, timeout_ms); + if (ret == -1) { + return knot_map_errno(); + } else if ((pfd.revents & POLLIN) == 0) { + return 0; + } + +#ifdef ENABLE_RECVMMSG + ret = recvmmsg(probe->fd, msgs, count, 0, NULL); +#else + ret = recvmsg(probe->fd, &msg, 0); +#endif + if (ret == -1) { + return knot_map_errno(); + } + +#ifdef ENABLE_RECVMMSG + return ret; +#else + return (ret > 0 ? 1 : 0); +#endif +} diff --git a/src/libknot/probe/probe.h b/src/libknot/probe/probe.h new file mode 100644 index 0000000..24c8067 --- /dev/null +++ b/src/libknot/probe/probe.h @@ -0,0 +1,116 @@ +/* Copyright (C) 2023 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 DNS traffic probe interface. + * + * \addtogroup probe + * @{ + */ + +#pragma once + +#include "libknot/probe/data.h" + +/*! A probe context. */ +struct knot_probe; +typedef struct knot_probe knot_probe_t; + +/*! + * Allocates a probe context. + * + * \return Probe context. + */ +knot_probe_t *knot_probe_alloc(void); + +/*! + * \brief Deallocates a probe. + * + * \param probe Probe context. + */ +void knot_probe_free(knot_probe_t *probe); + +/*! + * \brief Initializes one probe producer. + * + * \param probe Probe context. + * \param dir Unix socket directory. + * \param idx Probe ID (counted from 1). + * + * \retval KNOT_EOK Success. + * \retval KNOT_ECONN Initial connection failed. + * \return KNOT_E* If error. + */ +int knot_probe_set_producer(knot_probe_t *probe, const char *dir, uint16_t idx); + +/*! + * \brief Initializes one probe consumer. + * + * \note The socket permissions are set to 0222! + * + * \param probe Probe context. + * \param dir Unix socket directory. + * \param idx Probe ID (counted from 1). + * + * \retval KNOT_EOK Success. + * \return KNOT_E* If error. + */ +int knot_probe_set_consumer(knot_probe_t *probe, const char *dir, uint16_t idx); + +/*! + * \brief Returns file descriptor of the probe. + * + * \param probe Probe context. + */ +int knot_probe_fd(knot_probe_t *probe); + +/*! + * \brief Sends data units to a probe. + * + * \note Data arrays of length > 1 are not supported yet. + * + * If send fails due to unconnected socket anf if not connected for at least + * 2 seconds, reconnection is attempted and if successful, the send operation + * is repeated. + * + * \param probe Probe context. + * \param data Array of data units. + * \param count Length of data unit array. + * + * \retval KNOT_EOK Success. + * \return KNOT_E* If error. + */ +int knot_probe_produce(knot_probe_t *probe, const knot_probe_data_t *data, uint8_t count); + +/*! + * \brief Receives data units from a probe. + * + * This function blocks on poll until a data unit is received or timeout is hit. + * + * \param probe Probe context. + * \param data Array of data units. + * \param count Length of data unit array. + * \param timeout_ms Poll timeout in milliseconds (-1 means infinity). + * + * \retval >= 0 Number of data units received. + * \return KNOT_E* If error. + */ +int knot_probe_consume(knot_probe_t *probe, knot_probe_data_t *data, uint8_t count, + int timeout_ms); + +/*! @} */ 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..03e0784 --- /dev/null +++ b/src/libknot/rdataset.c @@ -0,0 +1,371 @@ +/* Copyright (C) 2022 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(rrs->rdata == NULL || (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_ +bool knot_rdataset_subset(const knot_rdataset_t *subset, const knot_rdataset_t *of) +{ + if (subset == NULL || (of != NULL && subset->rdata == of->rdata)) { + return true; + } + + knot_rdata_t *rd = subset->rdata; + for (uint16_t i = 0; i < subset->count; ++i) { + if (!knot_rdataset_member(of, rd)) { + return false; + } + rd = knot_rdataset_next(rd); + } + + return true; +} + +_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_intersect2(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) { + return KNOT_EOK; + } + + knot_rdata_t *rr1 = from->rdata; + for (uint16_t i = 0; i < from->count; ) { + if (!knot_rdataset_member(what, rr1)) { + int ret = remove_rr_at(from, i, mm); + if (ret != KNOT_EOK) { + return ret; + } + if (i < from->count) { + // Just to make sure rr1 remains valid if re-allocated. + rr1 = rr_seek(from, i); + } + } else { + i++; + 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..6d98086 --- /dev/null +++ b/src/libknot/rdataset.h @@ -0,0 +1,218 @@ +/* Copyright (C) 2022 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 Returns true if \a subset is a sub-set of \a of, false otherwise. + * + * \param subset RRS to check. + * \param of RRS to search in. + * + * \retval true if \a subset is a sub-set of \a of. + * \retval false if \a subset is not a sub-set of \a of. + */ +bool knot_rdataset_subset(const knot_rdataset_t *subset, const knot_rdataset_t *of); + +/*! + * \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 intersection. \a from RRS is changed. + * + * \param from RRS to be modified by intersection. + * \param what RRS to intersect. + * \param mm Memory context use to reallocated \a from data. + * + * \return KNOT_E* + */ +int knot_rdataset_intersect2(knot_rdataset_t *from, const knot_rdataset_t *what, + 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..dd814de --- /dev/null +++ b/src/libknot/rrset-dump.c @@ -0,0 +1,2292 @@ +/* Copyright (C) 2023 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/key.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/color.h" +#include "contrib/ctype.h" +#include "contrib/musl/inet_ntop.h" +#include "contrib/time.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_timestamp = true, + .hide_crypto = false, + .ascii_to_idn = NULL, + .color = NULL, + .now = 0, +}; + +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 (knot_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 (knot_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, + unsigned with_header_len, bool alpn_mode) +{ + CHECK_PRET + + size_t in_len = 0; + + CHECK_INMAX(with_header_len) + switch (with_header_len) { + case 0: + in_len = p->in_max; + break; + case 1: + in_len = *(p->in); + break; + case 2: + in_len = knot_wire_read_u16(p->in); + break; + default: + assert(0); + } + p->in += with_header_len; + p->in_max -= with_header_len; + CHECK_INMAX(in_len) + + // 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 + } + if (alpn_mode && (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 (sizeof(time_t) > 4) { + timestamp = knot_time_from_u32(timestamp, p->style->now); + } + + if (p->style->human_timestamp) { + struct tm result; + // Write timestamp in YYYYMMDDhhmmss format. + ret = strftime(p->out, p->out_max, "%Y%m%d%H%M%S", + gmtime_r(×tamp, &result)); + CHECK_RET_POSITIVE + } else { + // Write timestamp only. + ret = snprintf(p->out, p->out_max, "%u", ntohl(data)); + CHECK_RET_OUTMAX_SNPRINTF + } + out_len = ret; + + // Fill in output. + p->in += in_len; + p->in_max -= in_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; +} + +static uint32_t wire_time_to_val(rrset_dump_params_t *p) +{ + uint32_t data; + size_t in_len = sizeof(data); + + if (p->ret < 0 || p->in_max < in_len || + memcpy(&data, p->in, in_len) == NULL) { + p->ret = -1; + return 0; + } + + return ntohl(data); +} + +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 = knot_time_print_human(ntohl(data), p->out, p->out_max, true); + 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 (knot_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 (knot_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 mantissa 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 void wire_svcb_paramkey_to_str(rrset_dump_params_t *p) +{ + uint16_t param_key = knot_wire_read_u16(p->in); + const knot_lookup_t *type = knot_lookup_by_id(knot_svcb_param_names, param_key); + + if (type != NULL) { + dump_string(p, type->name); + CHECK_PRET + p->in += sizeof(param_key); + p->in_max -= sizeof(param_key); + } else { + dump_string(p, "key"); + CHECK_PRET + wire_num16_to_str(p); + CHECK_PRET + } +} + +static void wire_value_list_to_str(rrset_dump_params_t *p, + void (*list_item_dump_fcn)(rrset_dump_params_t *p), + const uint8_t *expect_end) +{ + bool first = true; + + while (expect_end > p->in) { + if (first) { + first = false; + } else { + dump_string(p, ","); + CHECK_PRET + } + + list_item_dump_fcn(p); + CHECK_PRET + } + if (expect_end != p->in) { + p->ret = -1; + } +} + +static void wire_text_to_str1(rrset_dump_params_t *p) +{ + wire_text_to_str(p, false, 1, true); +} + +static void wire_ech_to_base64(rrset_dump_params_t *p, unsigned ech_len) +{ + CHECK_INMAX(ech_len) + + int ret = knot_base64_encode(p->in, ech_len, (uint8_t *)(p->out), p->out_max); + CHECK_RET_POSITIVE + size_t out_len = ret; + + p->in += ech_len; + p->in_max -= ech_len; + p->out += out_len; + p->out_max -= out_len; + p->total += out_len; + + STRING_TERMINATION +} + +static void wire_svcparam_to_str(rrset_dump_params_t *p) +{ + CHECK_PRET + + CHECK_INMAX(4) + + // Pre-fetch key and length for later use. + uint16_t key_type = knot_wire_read_u16(p->in); + uint16_t val_len = knot_wire_read_u16(p->in + sizeof(key_type)); + + wire_svcb_paramkey_to_str(p); + + p->in += sizeof(val_len); + p->in_max -= sizeof(val_len); + CHECK_INMAX(val_len) + + if (val_len > 0) { + dump_string(p, "="); + CHECK_PRET + + switch (key_type) { + case KNOT_SVCB_PARAM_MANDATORY: + wire_value_list_to_str(p, wire_svcb_paramkey_to_str, p->in + val_len); + break; + case KNOT_SVCB_PARAM_ALPN: + wire_value_list_to_str(p, wire_text_to_str1, p->in + val_len); + break; + case KNOT_SVCB_PARAM_NDALPN: + p->ret = -1; // must not have value + break; + case KNOT_SVCB_PARAM_PORT: + if (val_len != sizeof(uint16_t)) { + p->ret = -1; + } else { + wire_num16_to_str(p); + } + break; + case KNOT_SVCB_PARAM_IPV4HINT: + wire_value_list_to_str(p, wire_ipv4_to_str, p->in + val_len); + break; + case KNOT_SVCB_PARAM_ECH: + wire_ech_to_base64(p, val_len); + break; + case KNOT_SVCB_PARAM_IPV6HINT: + wire_value_list_to_str(p, wire_ipv6_to_str, p->in + val_len); + break; + default: + p->in -= sizeof(val_len); // Rollback to where the string length resides. + p->in_max += sizeof(val_len); + wire_text_to_str(p, true, sizeof(val_len), false); + } + } +} + +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 DNSSEC_KEY_ALGORITHM_DSA: + case DNSSEC_KEY_ALGORITHM_DSA_NSEC3_SHA1: + // RFC 2536, key size ~ bit-length of 'modulus' P. + return (64 + 8 * key[0]) * 8; + case DNSSEC_KEY_ALGORITHM_RSA_MD5: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + // 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 DNSSEC_KEY_ALGORITHM_ECC_GOST: + // RFC 5933, key size of GOST public keys MUST be 512 bits. + return 512; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + // RFC 6605. + return 256; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + // RFC 6605. + return 384; + case DNSSEC_KEY_ALGORITHM_ED25519: + // RFC 8080. + return 256; + case DNSSEC_KEY_ALGORITHM_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 DNSSEC_KEY_ALGORITHM_DELETE: + case DNSSEC_KEY_ALGORITHM_INDIRECT: + break; + case DNSSEC_KEY_ALGORITHM_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 DNSSEC_KEY_ALGORITHM_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 STORE_TIME if (p->style->verbose) { \ + time = wire_time_to_val(p); CHECK_RET(p); \ + } +#define COMMENT_TIME(s) if (p->style->verbose) { \ + char buf[80]; \ + dump_string(p, " ; "); CHECK_RET(p); \ + dump_string(p, s); CHECK_RET(p); \ + if (knot_time_print_human(time, buf, sizeof(buf), false) > 0) { \ + dump_string(p, " ("); CHECK_RET(p); \ + dump_string(p, buf); CHECK_RET(p); \ + dump_string(p, ")"); 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, 1, false); CHECK_RET(p); +#define DUMP_LONG_TEXT wire_text_to_str(p, true, 0, false); CHECK_RET(p); +#define DUMP_UNQUOTED wire_text_to_str(p, false, 1, false); 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_SVCPARAM wire_svcparam_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) { + uint32_t time = 0; + DUMP_DNAME; DUMP_SPACE; + DUMP_DNAME; DUMP_SPACE; WRAP_INIT; + DUMP_NUM32; COMMENT("serial"); WRAP_LINE; + STORE_TIME; DUMP_TIME; COMMENT_TIME("refresh"); WRAP_LINE; + STORE_TIME; DUMP_TIME; COMMENT_TIME("retry"); WRAP_LINE; + STORE_TIME; DUMP_TIME; COMMENT_TIME("expire"); WRAP_LINE; + STORE_TIME; DUMP_TIME; COMMENT_TIME("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; + if (p->style->wrap) { + WRAP_LINE; + } else { + DUMP_SPACE; + } + 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; 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; 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_svcb(DUMP_PARAMS) +{ + DUMP_NUM16; DUMP_SPACE; + DUMP_DNAME; + if (p->style->wrap) { + if (p->in_max > 0) { + DUMP_SPACE; + WRAP_INIT; + DUMP_SVCPARAM; + while (p->in_max > 0) { + WRAP_LINE; DUMP_SVCPARAM; + } + WRAP_END; + } + } else { + while (p->in_max > 0) { + DUMP_SPACE; + DUMP_SVCPARAM; + } + } + + 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); + case KNOT_RRTYPE_SVCB: + case KNOT_RRTYPE_HTTPS: + return dump_svcb(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 (knot_time_print_human(ttl, buf, sizeof(buf), true) < 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; + size_t color_len = (style->color != NULL ? strlen(style->color) : 0); + size_t reset_len = (color_len > 0 ? strlen(COL_RST(true)) : 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++) { + // Put color prefix before every record. + if (color_len > 0) { + if (len >= maxlen - color_len) { + return KNOT_ESPACE; + } + memcpy(dst + len, style->color, color_len); + len += color_len; + } + + // 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; + + // Reset the color. + if (reset_len > 0) { + if (len >= maxlen - reset_len) { + return KNOT_ESPACE; + } + memcpy(dst + len, COL_RST(true), reset_len); + len += reset_len; + } + + // 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..4631531 --- /dev/null +++ b/src/libknot/rrset-dump.h @@ -0,0 +1,118 @@ +/* Copyright (C) 2022 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_timestamp; + /*!< 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); + /*!< Optional color control sequence which is put before every output line. + * Not compatible with wrap. */ + const char *color; + /*!< Time context for correct > 32 bit timestamp conversion. */ + uint64_t now; +} 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..294689c --- /dev/null +++ b/src/libknot/rrtype/opt.c @@ -0,0 +1,687 @@ +/* Copyright (C) 2022 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; + } + + if (size > 0) { + 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; +} diff --git a/src/libknot/rrtype/opt.h b/src/libknot/rrtype/opt.h new file mode 100644 index 0000000..bce87c2 --- /dev/null +++ b/src/libknot/rrtype/opt.h @@ -0,0 +1,587 @@ +/* Copyright (C) 2022 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_DEFAULT = 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 Expire option code. */ + KNOT_EDNS_OPTION_EXPIRE = 9, + /*! \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 EDNS Extended error code. */ + KNOT_EDNS_OPTION_EDE = 15, + /*! \brief The minimal length for EDE option including option header. */ + KNOT_EDNS_EDE_MIN_LENGTH = 6, + + /*! \brief Maximal currently known option code. */ + KNOT_EDNS_MAX_OPTION_CODE = 17, +}; + +/* 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); + +/*! @} */ diff --git a/src/libknot/rrtype/rdname.h b/src/libknot/rrtype/rdname.h new file mode 100644 index 0000000..d2be870 --- /dev/null +++ b/src/libknot/rrtype/rdname.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2021 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" +#include "libknot/rrtype/svcb.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); + case KNOT_RRTYPE_SVCB: + case KNOT_RRTYPE_HTTPS: + return knot_svcb_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/svcb.h b/src/libknot/rrtype/svcb.h new file mode 100644 index 0000000..abee597 --- /dev/null +++ b/src/libknot/rrtype/svcb.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2021 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 +uint32_t knot_svcb_priority(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u16(rdata->data); +} + +static inline +const knot_dname_t *knot_svcb_target(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 2; +} + +/*! @} */ diff --git a/src/libknot/rrtype/tsig.c b/src/libknot/rrtype/tsig.c new file mode 100644 index 0000000..83f8436 --- /dev/null +++ b/src/libknot/rrtype/tsig.c @@ -0,0 +1,409 @@ +/* Copyright (C) 2021 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, 2 * sizeof(uint16_t)); + assert(nb == 0); + nb = wire_ctx_read_u16(&wire); + if (wire_ctx_available(&wire) != nb) { + return NULL; + } + 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. */ + if (len > 0) { + 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) +{ + 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); +} diff --git a/src/libknot/rrtype/tsig.h b/src/libknot/rrtype/tsig.h new file mode 100644 index 0000000..083a063 --- /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) //fudge + + 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/rrtype/zonemd.h b/src/libknot/rrtype/zonemd.h new file mode 100644 index 0000000..a4d734b --- /dev/null +++ b/src/libknot/rrtype/zonemd.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2021 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" + +#define KNOT_ZONEMD_SCHEME_SIMPLE 1 +#define KNOT_ZONEMD_ALGORITHM_SHA384 1 +#define KNOT_ZONEMD_ALGORITHM_SHA512 2 + +static inline +uint32_t knot_zonemd_soa_serial(const knot_rdata_t *rdata) +{ + assert(rdata); + return knot_wire_read_u32(rdata->data); +} + +static inline +uint8_t knot_zonemd_scheme(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 4); +} + +static inline +uint8_t knot_zonemd_algorithm(const knot_rdata_t *rdata) +{ + assert(rdata); + return *(rdata->data + 5); +} + +static inline +size_t knot_zonemd_digest_size(const knot_rdata_t *rdata) +{ + switch (knot_zonemd_algorithm(rdata)) { + case KNOT_ZONEMD_ALGORITHM_SHA384: return 48; + case KNOT_ZONEMD_ALGORITHM_SHA512: return 64; + default: return 0; + } +} + +static inline +const uint8_t *knot_zonemd_digest(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data + 6; +} + +/*! @} */ diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c new file mode 100644 index 0000000..7913859 --- /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 variables. + */ + 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 variables. + */ + 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..35311a2 --- /dev/null +++ b/src/libknot/tsig.c @@ -0,0 +1,188 @@ +/* Copyright (C) 2021 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); + + if (key->secret.data) { + memzero(key->secret.data, key->secret.size); + } + dnssec_binary_free(&key->secret); + + memzero(key, sizeof(*key)); +} + +_public_ +int knot_tsig_key_init(knot_tsig_key_t *key, const char *algorithm_name, + const char *name, const char *secret_b64) +{ + if (!name || !secret_b64 || !key) { + return KNOT_EINVAL; + } + + dnssec_tsig_algorithm_t algorithm = DNSSEC_TSIG_HMAC_SHA256; + if (algorithm_name != NULL) { + algorithm = dnssec_tsig_algorithm_from_name(algorithm_name); + if (algorithm == DNSSEC_TSIG_UNKNOWN) { + return KNOT_EMALF; + } + } + + knot_dname_t *dname = knot_dname_from_str_alloc(name); + if (!dname) { + return KNOT_ENOMEM; + } + knot_dname_to_lower(dname); + + dnssec_binary_t b64secret = { 0 }; + b64secret.data = (uint8_t *)secret_b64; + b64secret.size = strlen(secret_b64); + + dnssec_binary_t secret = { 0 }; + int result = dnssec_binary_from_base64(&b64secret, &secret); + if (result != KNOT_EOK) { + knot_dname_free(dname, NULL); + return result; + } + + key->name = dname; + key->algorithm = algorithm; + key->secret = secret; + + return KNOT_EOK; +} + +_public_ +int knot_tsig_key_init_str(knot_tsig_key_t *key, const char *params) +{ + if (!params) { + return KNOT_EINVAL; + } + + char *copy = strstrip(params); + if (!copy) { + return KNOT_ENOMEM; + } + + size_t copy_size = strlen(copy) + 1; + + // format [algorithm:]name:secret + + char *algorithm = NULL; + char *name = NULL; + char *secret = NULL; + + // find secret + + char *pos = strrchr(copy, ':'); + if (pos) { + *pos = '\0'; + secret = pos + 1; + } else { + memzero(copy, copy_size); + free(copy); + return KNOT_EMALF; + } + + // find name and optionally algorithm + + pos = strchr(copy, ':'); + if (pos) { + *pos = '\0'; + algorithm = copy; + name = pos + 1; + } else { + name = copy; + } + + int result = knot_tsig_key_init(key, algorithm, name, secret); + + memzero(copy, copy_size); + free(copy); + + return result; +} + +_public_ +int knot_tsig_key_init_file(knot_tsig_key_t *key, const char *filename) +{ + if (!filename) { + return KNOT_EINVAL; + } + + FILE *file = fopen(filename, "r"); + if (!file) { + return KNOT_EACCES; + } + + char *line = NULL; + size_t line_size = 0; + ssize_t read = knot_getline(&line, &line_size, file); + + fclose(file); + + if (read == -1) { + return KNOT_EMALF; + } + + int result = knot_tsig_key_init_str(key, line); + + memzero(line, line_size); + free(line); + + return result; +} + +_public_ +int knot_tsig_key_copy(knot_tsig_key_t *dst, const knot_tsig_key_t *src) +{ + if (!src || !dst) { + return KNOT_EINVAL; + } + + knot_tsig_key_t copy = { 0 }; + copy.algorithm = src->algorithm; + + copy.name = knot_dname_copy(src->name, NULL); + if (!copy.name) { + return KNOT_ENOMEM; + } + + if (dnssec_binary_dup(&src->secret, ©.secret) != DNSSEC_EOK) { + knot_tsig_key_deinit(©); + return KNOT_ENOMEM; + } + + *dst = copy; + + return KNOT_EOK; +} diff --git a/src/libknot/tsig.h b/src/libknot/tsig.h new file mode 100644 index 0000000..abbdde2 --- /dev/null +++ b/src/libknot/tsig.h @@ -0,0 +1,89 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \brief TSIG operations + * + * \addtogroup knot-tsig + * @{ + */ + +#pragma once + +#include "libknot/dname.h" +#include "libdnssec/tsig.h" + +/*! + * \brief TSIG key. + */ +typedef struct { + dnssec_tsig_algorithm_t algorithm; + knot_dname_t *name; + dnssec_binary_t secret; +} knot_tsig_key_t; + +/*! + * \brief Packet signing context. + */ +typedef struct { + knot_tsig_key_t tsig_key; + uint8_t *tsig_digest; + size_t tsig_digestlen; + uint64_t tsig_time_signed; + size_t pkt_count; +} knot_sign_context_t; + +/*! + * \brief Initialize a new TSIG key from individual key parameters. + * + * \param[out] key Key to be initialized. + * \param[in] algorithm Algorithm name. NULL for default (hmac-md5). + * \param[in] name Key name (domain name in presentation format). + * \param[in] secret_b64 Secret encoded using Base 64. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_tsig_key_init(knot_tsig_key_t *key, const char *algorithm, + const char *name, const char *secret_b64); + +/*! + * \brief Create a new TSIG key from a string encoding all parameters. + * + * \param[out] key Key to be initialized. + * \param[in] params Parameters in a form \a [algorithm:]name:base64_secret + */ +int knot_tsig_key_init_str(knot_tsig_key_t *key, const char *params); + +/*! + * \brief Create a new TSIG key by reading the parameters from a file. + * + * The file content is parsed by \a tsig_key_create_str. + */ +int knot_tsig_key_init_file(knot_tsig_key_t *key, const char *filename); + +/*! + * \brief Deinitialize TSIG key. + */ +void knot_tsig_key_deinit(knot_tsig_key_t *key); + +/*! + * \brief Duplicate a TSIG key. + */ +int knot_tsig_key_copy(knot_tsig_key_t *dst, const knot_tsig_key_t *src); + +/*! @} */ diff --git a/src/libknot/version.h b/src/libknot/version.h new file mode 100644 index 0000000..49aee67 --- /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 2 +#define KNOT_VERSION_PATCH 0x06 + +#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.h b/src/libknot/xdp.h new file mode 100644 index 0000000..db4460c --- /dev/null +++ b/src/libknot/xdp.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2021 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 XDP-related stuff. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +#if ENABLE_XDP +#include "libknot/xdp/xdp.h" +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/eth.h" +#include "libknot/xdp/tcp.h" +#endif + +/*! @} */ diff --git a/src/libknot/xdp/Makefile.am b/src/libknot/xdp/Makefile.am new file mode 100644 index 0000000..cb58438 --- /dev/null +++ b/src/libknot/xdp/Makefile.am @@ -0,0 +1,20 @@ +# 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 +# +# Built using LLVM/CLANG 14. +# +# When updating check using `llvm-objdump -h bpf-kernel.o` if .BTF and .BTF.ext +# sections are present. + +EXTRA_DIST = bpf-kernel-obj.c bpf-kernel.c + +.PHONY: filter + +filter: + rm -f bpf-kernel.o bpf-kernel-obj.c + clang -target bpf -Wall -O2 -g -DNDEBUG -c -o bpf-kernel.o -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c + llvm-strip -S bpf-kernel.o + 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..b56c39d --- /dev/null +++ b/src/libknot/xdp/Makefile.in @@ -0,0 +1,545 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 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 +# +# Built using LLVM/CLANG 14. +# +# When updating check using `llvm-objdump -h bpf-kernel.o` if .BTF and .BTF.ext +# sections are present. +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_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.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@ +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_NO_UNDEFINED = @LT_NO_UNDEFINED@ +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@ +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@ +embedded_libngtcp2_CFLAGS = @embedded_libngtcp2_CFLAGS@ +embedded_libngtcp2_LIBS = @embedded_libngtcp2_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@ +libkqueue_CFLAGS = @libkqueue_CFLAGS@ +libkqueue_LIBS = @libkqueue_LIBS@ +libmaxminddb_CFLAGS = @libmaxminddb_CFLAGS@ +libmaxminddb_LIBS = @libmaxminddb_LIBS@ +libmnl_CFLAGS = @libmnl_CFLAGS@ +libmnl_LIBS = @libmnl_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ +libngtcp2_CFLAGS = @libngtcp2_CFLAGS@ +libngtcp2_LIBS = @libngtcp2_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@ +libxdp_CFLAGS = @libxdp_CFLAGS@ +libxdp_LIBS = @libxdp_LIBS@ +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.o bpf-kernel-obj.c + clang -target bpf -Wall -O2 -g -DNDEBUG -c -o bpf-kernel.o -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c + llvm-strip -S bpf-kernel.o + 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..3b92cbb --- /dev/null +++ b/src/libknot/xdp/bpf-consts.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2022 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 XDP filter configuration constants. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <linux/types.h> + +#define KNOT_XDP_PKT_ALIGNMENT 2 /*!< Fix for misaligned access to packet structures. */ + +/*! \brief XDP filter configuration flags. */ +typedef enum { + KNOT_XDP_FILTER_ON = 1 << 0, /*!< Filter enabled. */ + KNOT_XDP_FILTER_UDP = 1 << 1, /*!< Apply filter to UDP. */ + KNOT_XDP_FILTER_TCP = 1 << 2, /*!< Apply filter to TCP. */ + KNOT_XDP_FILTER_QUIC = 1 << 3, /*!< Apply filter to QUIC/UDP. */ + KNOT_XDP_FILTER_PASS = 1 << 4, /*!< Pass incoming messages to ports >= port value. */ + KNOT_XDP_FILTER_DROP = 1 << 5, /*!< Drop incoming messages to ports >= port value. */ + KNOT_XDP_FILTER_ROUTE = 1 << 6, /*!< Consider routing information from kernel. */ +} knot_xdp_filter_flag_t; + +/*! \brief XDP map item for the filter configuration. */ +typedef struct knot_xdp_opts knot_xdp_opts_t; +struct knot_xdp_opts { + __u16 flags; /*!< XDP filter flags \a knot_xdp_filter_flag_t. */ + __u16 udp_port; /*!< UDP/TCP port to listen on. */ + __u16 quic_port; /*!< QUIC/UDP port to listen on. */ +} __attribute__((packed)); + +/*! \brief Additional information from the filter. */ +typedef struct knot_xdp_info knot_xdp_info_t; +struct knot_xdp_info { + __u16 out_if_index; /*!< Index of the output interface (if routing enabled). */ +}; + +/*! @} */ diff --git a/src/libknot/xdp/bpf-kernel-obj.c b/src/libknot/xdp/bpf-kernel-obj.c new file mode 100644 index 0000000..9f1ee71 --- /dev/null +++ b/src/libknot/xdp/bpf-kernel-obj.c @@ -0,0 +1,941 @@ +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, 0xb8, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x0d, 0x00, 0x01, 0x00, 0xbf, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 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, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x71, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x19, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x09, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x29, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x02, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x12, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xa0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x2a, 0x98, 0xff, 0x00, 0x00, 0x00, 0x00, 0x71, 0x18, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x11, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x1a, 0xa8, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x61, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x61, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x72, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x3a, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x62, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x21, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x74, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x73, 0x0d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x4f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x0b, 0x00, + 0x81, 0x00, 0x00, 0x00, 0xbf, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x21, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xb0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x03, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x74, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x73, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x4f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x15, 0x03, 0x1e, 0x00, 0x86, 0xdd, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x55, 0x03, 0xdc, 0x00, + 0x08, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x04, 0xd3, 0x00, + 0x40, 0x00, 0x00, 0x00, 0xbf, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x15, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xdc, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x45, 0xcd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x05, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x4a, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x05, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x10, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2d, 0x25, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x03, 0xb8, 0x00, + 0x60, 0x00, 0x00, 0x00, 0xbf, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x14, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x07, 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x34, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7b, 0x3a, 0x90, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x10, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x08, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x2d, 0x25, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x71, 0x10, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x7b, 0x3a, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00, 0x67, 0x08, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xa8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x4f, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x03, 0x17, 0x00, 0x11, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x55, 0x03, 0x9d, 0x00, + 0x06, 0x00, 0x00, 0x00, 0xbf, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x02, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x52, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xbf, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x1d, 0x32, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x03, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x08, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x2d, 0x28, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x53, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xdc, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x23, 0x7f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x52, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xdc, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x1d, 0x32, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x15, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x08, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x3d, 0x82, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x03, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xa0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x79, 0xa5, 0x98, 0xff, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x03, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x1d, 0x32, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x03, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x05, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x2d, 0x25, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x55, 0x02, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x55, 0x04, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x09, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x15, 0x09, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x2a, 0xe8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xe0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x2a, 0xd0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc8, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xc0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xa2, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x55, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x73, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xc8, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x1a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x16, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x73, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x1c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x61, 0x13, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x32, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x12, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x61, 0x13, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xd0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x61, 0x13, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7b, 0x2a, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x4f, 0x21, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7b, 0x1a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00, + 0xb8, 0xff, 0xff, 0xff, 0xbf, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x01, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x15, 0x01, 0x24, 0x00, 0x07, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x21, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x55, 0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x69, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x69, 0xa2, 0xec, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x1c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x69, 0xa2, 0xee, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x19, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x71, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x69, 0xa2, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x16, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xa1, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x15, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xa1, 0xc0, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xa2, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x6b, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xa1, 0xf6, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x73, 0x17, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x73, 0x17, 0x0b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x69, 0xa1, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x17, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x73, 0x17, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x69, 0xa1, 0xf2, 0xff, 0x00, 0x00, 0x00, 0x00, 0x73, 0x17, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x73, 0x17, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x62, 0x10, 0x00, + 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, 0xb7, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0xfd, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0xfb, 0xff, + 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, 0x00, 0x00, 0x47, 0x50, 0x4c, 0x00, + 0x9f, 0xeb, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7c, 0x02, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0xce, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x12, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x04, + 0x18, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0xa0, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x14, 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x0d, 0x02, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0c, + 0x15, 0x00, 0x00, 0x00, 0xb2, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xb7, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc0, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0xc6, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x69, 0x6e, 0x74, 0x00, 0x5f, 0x5f, 0x41, 0x52, 0x52, 0x41, 0x59, + 0x5f, 0x53, 0x49, 0x5a, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x5f, + 0x00, 0x74, 0x79, 0x70, 0x65, 0x00, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x00, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x00, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x00, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78, + 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78, 0x64, 0x70, 0x5f, + 0x6d, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x64, 0x61, 0x74, 0x61, + 0x5f, 0x65, 0x6e, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65, + 0x74, 0x61, 0x00, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, + 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x72, 0x78, 0x5f, 0x71, 0x75, + 0x65, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x65, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x00, 0x5f, 0x5f, 0x75, 0x33, 0x32, 0x00, 0x75, 0x6e, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x00, 0x63, 0x74, 0x78, 0x00, + 0x78, 0x64, 0x70, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x5f, 0x64, 0x6e, 0x73, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x78, 0x64, + 0x70, 0x00, 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x64, 0x73, 0x61, 0x6c, + 0x7a, 0x6d, 0x61, 0x6e, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x32, 0x2f, 0x73, + 0x72, 0x63, 0x2f, 0x6c, 0x69, 0x62, 0x6b, 0x6e, 0x6f, 0x74, 0x2f, 0x78, + 0x64, 0x70, 0x2f, 0x62, 0x70, 0x66, 0x2d, 0x6b, 0x65, 0x72, 0x6e, 0x65, + 0x6c, 0x2e, 0x63, 0x00, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x64, 0x70, 0x5f, + 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x6e, 0x73, + 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x28, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x20, 0x78, 0x64, 0x70, 0x5f, 0x6d, 0x64, 0x20, 0x2a, 0x63, 0x74, 0x78, + 0x29, 0x00, 0x09, 0x5f, 0x5f, 0x75, 0x33, 0x32, 0x20, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x20, 0x3d, 0x20, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x72, 0x78, + 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x3b, 0x00, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6b, 0x6e, + 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x6f, 0x70, 0x74, 0x73, 0x20, + 0x2a, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x70, 0x74, 0x72, 0x20, 0x3d, 0x20, + 0x62, 0x70, 0x66, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x5f, 0x65, 0x6c, 0x65, 0x6d, 0x28, 0x26, 0x6f, 0x70, 0x74, + 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2c, 0x20, 0x26, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x29, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x21, 0x6f, 0x70, + 0x74, 0x73, 0x5f, 0x70, 0x74, 0x72, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x69, + 0x66, 0x20, 0x28, 0x21, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58, + 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x4f, 0x4e, + 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x29, + 0x62, 0x70, 0x66, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x61, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x28, 0x63, 0x74, 0x78, 0x2c, + 0x20, 0x2d, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x29, 0x73, 0x69, 0x7a, 0x65, + 0x6f, 0x66, 0x28, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6b, 0x6e, + 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x29, + 0x00, 0x09, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x64, 0x61, 0x74, 0x61, + 0x20, 0x3d, 0x20, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28, + 0x6c, 0x6f, 0x6e, 0x67, 0x29, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61, + 0x74, 0x61, 0x3b, 0x00, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, + 0x6b, 0x6e, 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x69, 0x6e, 0x66, + 0x6f, 0x20, 0x2a, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x28, 0x76, + 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28, 0x6c, 0x6f, 0x6e, 0x67, 0x29, + 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65, + 0x74, 0x61, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76, 0x6f, + 0x69, 0x64, 0x20, 0x2a, 0x29, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x2b, 0x20, + 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x6d, 0x65, 0x74, 0x61, + 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x29, 0x20, 0x7b, 0x00, + 0x09, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x76, 0x6f, 0x69, 0x64, 0x20, + 0x2a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x3d, 0x20, + 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28, 0x6c, 0x6f, 0x6e, + 0x67, 0x29, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x65, 0x6e, 0x64, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76, + 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64, + 0x72, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, + 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64, 0x72, 0x29, 0x20, 0x3e, 0x20, 0x64, + 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, + 0x69, 0x66, 0x20, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64, 0x72, 0x2d, + 0x3e, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x3d, 0x3d, 0x20, + 0x5f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x5f, 0x68, + 0x74, 0x6f, 0x6e, 0x73, 0x28, 0x45, 0x54, 0x48, 0x5f, 0x50, 0x5f, 0x38, + 0x30, 0x32, 0x31, 0x51, 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, + 0x66, 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20, 0x73, 0x69, + 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x5f, 0x5f, 0x75, 0x31, 0x36, 0x29, 0x20, + 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x65, 0x74, 0x68, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x7d, + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x6d, 0x65, + 0x74, 0x61, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x7b, 0x00, 0x09, + 0x09, 0x5f, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x5f, 0x6d, + 0x65, 0x6d, 0x63, 0x70, 0x79, 0x28, 0x26, 0x65, 0x74, 0x68, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20, + 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x5f, 0x5f, 0x75, 0x31, 0x36, + 0x29, 0x2c, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x65, 0x74, + 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x29, 0x29, 0x3b, 0x00, 0x09, 0x73, + 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, + 0x28, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x69, 0x70, 0x34, + 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x69, + 0x70, 0x34, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, + 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, + 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x21, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, + 0x66, 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, + 0x2d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3c, 0x20, 0x5f, 0x5f, 0x62, + 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x69, 0x70, 0x34, + 0x2d, 0x3e, 0x74, 0x6f, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x29, 0x29, 0x20, + 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x70, 0x34, 0x2d, + 0x3e, 0x66, 0x72, 0x61, 0x67, 0x5f, 0x6f, 0x66, 0x66, 0x20, 0x21, 0x3d, + 0x20, 0x30, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, 0x6c, 0x34, 0x5f, 0x68, + 0x64, 0x72, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20, + 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x69, 0x68, 0x6c, 0x20, 0x2a, 0x20, 0x34, + 0x3b, 0x00, 0x09, 0x09, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x20, 0x3d, 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x3b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, + 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x69, 0x70, 0x36, 0x20, + 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x69, 0x70, + 0x36, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, + 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, + 0x70, 0x36, 0x2d, 0x3e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, + 0x21, 0x3d, 0x20, 0x36, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, + 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x2d, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3c, 0x20, 0x5f, 0x5f, 0x62, 0x70, + 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x69, 0x70, 0x36, 0x2d, + 0x3e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x6c, 0x65, 0x6e, + 0x29, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, + 0x69, 0x70, 0x36, 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x70, + 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x36, + 0x2d, 0x3e, 0x6e, 0x65, 0x78, 0x74, 0x68, 0x64, 0x72, 0x3b, 0x00, 0x09, + 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x20, 0x3d, 0x3d, 0x20, 0x49, 0x50, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x5f, 0x46, 0x52, 0x41, 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x29, 0x20, 0x7b, + 0x00, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76, 0x6f, 0x69, + 0x64, 0x20, 0x2a, 0x29, 0x66, 0x72, 0x61, 0x67, 0x20, 0x2b, 0x20, 0x73, + 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x66, 0x72, 0x61, 0x67, 0x29, + 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, + 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x20, 0x3d, 0x20, 0x66, 0x72, 0x61, 0x67, 0x2d, 0x3e, 0x6e, + 0x65, 0x78, 0x74, 0x68, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x73, 0x77, 0x69, + 0x74, 0x63, 0x68, 0x20, 0x28, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6c, + 0x34, 0x5f, 0x68, 0x64, 0x72, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, + 0x6f, 0x66, 0x28, 0x2a, 0x74, 0x63, 0x70, 0x29, 0x20, 0x3e, 0x20, 0x64, + 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, + 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, + 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, + 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, + 0x43, 0x50, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, 0x70, 0x6f, 0x72, + 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x5f, 0x5f, 0x62, + 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x74, 0x63, 0x70, + 0x2d, 0x3e, 0x64, 0x65, 0x73, 0x74, 0x29, 0x3b, 0x00, 0x09, 0x09, 0x20, + 0x20, 0x20, 0x20, 0x28, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, + 0x74, 0x20, 0x3d, 0x3d, 0x20, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x75, 0x64, + 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x09, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, + 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x28, 0x4b, 0x4e, 0x4f, + 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, + 0x5f, 0x50, 0x41, 0x53, 0x53, 0x20, 0x7c, 0x20, 0x4b, 0x4e, 0x4f, 0x54, + 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, + 0x44, 0x52, 0x4f, 0x50, 0x29, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, + 0x69, 0x66, 0x20, 0x28, 0x6c, 0x34, 0x5f, 0x68, 0x64, 0x72, 0x20, 0x2b, + 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x75, 0x64, 0x70, + 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, + 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, 0x61, + 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x2d, 0x20, 0x28, 0x76, 0x6f, + 0x69, 0x64, 0x20, 0x2a, 0x29, 0x75, 0x64, 0x70, 0x20, 0x3c, 0x20, 0x5f, + 0x5f, 0x62, 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x75, + 0x64, 0x70, 0x2d, 0x3e, 0x6c, 0x65, 0x6e, 0x29, 0x29, 0x20, 0x7b, 0x00, + 0x09, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20, + 0x3d, 0x20, 0x5f, 0x5f, 0x62, 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, + 0x73, 0x28, 0x75, 0x64, 0x70, 0x2d, 0x3e, 0x64, 0x65, 0x73, 0x74, 0x29, + 0x3b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74, + 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, + 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, + 0x52, 0x5f, 0x55, 0x44, 0x50, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, + 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, + 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, + 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, + 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x51, 0x55, 0x49, 0x43, 0x29, 0x20, 0x26, + 0x26, 0x00, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x28, 0x70, 0x6f, 0x72, + 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20, 0x3d, 0x3d, 0x20, 0x6f, 0x70, + 0x74, 0x73, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74, + 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, + 0x69, 0x66, 0x20, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61, + 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44, + 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x44, 0x52, 0x4f, + 0x50, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6f, 0x70, + 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, + 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, + 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x29, 0x20, 0x7b, 0x00, + 0x09, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x62, 0x70, 0x66, + 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, + 0x66, 0x69, 0x62, 0x20, 0x3d, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, + 0x20, 0x28, 0x69, 0x70, 0x76, 0x34, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, + 0x09, 0x66, 0x69, 0x62, 0x2e, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x20, + 0x20, 0x20, 0x3d, 0x20, 0x41, 0x46, 0x5f, 0x49, 0x4e, 0x45, 0x54, 0x3b, + 0x00, 0x09, 0x09, 0x09, 0x66, 0x69, 0x62, 0x2e, 0x69, 0x70, 0x76, 0x34, + 0x5f, 0x73, 0x72, 0x63, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e, + 0x64, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x66, 0x69, + 0x62, 0x2e, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x64, 0x73, 0x74, 0x20, 0x3d, + 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x73, 0x61, 0x64, 0x64, 0x72, 0x3b, + 0x00, 0x09, 0x09, 0x09, 0x66, 0x69, 0x62, 0x2e, 0x66, 0x61, 0x6d, 0x69, + 0x6c, 0x79, 0x20, 0x3d, 0x20, 0x41, 0x46, 0x5f, 0x49, 0x4e, 0x45, 0x54, + 0x36, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x2a, 0x69, 0x70, 0x76, 0x36, 0x5f, + 0x73, 0x72, 0x63, 0x20, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x36, 0x2d, 0x3e, + 0x64, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x2a, 0x69, + 0x70, 0x76, 0x36, 0x5f, 0x64, 0x73, 0x74, 0x20, 0x20, 0x3d, 0x20, 0x69, + 0x70, 0x36, 0x2d, 0x3e, 0x73, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09, + 0x09, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x62, + 0x70, 0x66, 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x28, 0x63, 0x74, 0x78, 0x2c, 0x20, 0x26, 0x66, 0x69, 0x62, 0x2c, + 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x66, 0x69, 0x62, 0x29, + 0x2c, 0x20, 0x42, 0x50, 0x46, 0x5f, 0x46, 0x49, 0x42, 0x5f, 0x4c, 0x4f, + 0x4f, 0x4b, 0x55, 0x50, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x29, + 0x3b, 0x00, 0x09, 0x09, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28, + 0x72, 0x65, 0x74, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09, 0x69, 0x66, + 0x20, 0x28, 0x6d, 0x61, 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x30, 0x5d, 0x20, + 0x21, 0x3d, 0x20, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x30, + 0x5d, 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, + 0x6d, 0x61, 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x31, 0x5d, 0x20, 0x21, 0x3d, + 0x20, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x31, 0x5d, 0x20, + 0x7c, 0x7c, 0x00, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, + 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x32, 0x5d, 0x20, 0x21, 0x3d, 0x20, 0x6d, + 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x32, 0x5d, 0x29, 0x20, 0x7b, + 0x00, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6d, 0x65, 0x74, 0x61, + 0x20, 0x21, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09, + 0x09, 0x6d, 0x65, 0x74, 0x61, 0x2d, 0x3e, 0x6f, 0x75, 0x74, 0x5f, 0x69, + 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x69, + 0x62, 0x2e, 0x69, 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3b, 0x00, 0x09, + 0x09, 0x09, 0x5f, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x5f, + 0x6d, 0x65, 0x6d, 0x63, 0x70, 0x79, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x68, + 0x64, 0x72, 0x2d, 0x3e, 0x68, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2c, 0x20, 0x66, 0x69, 0x62, 0x2e, 0x64, 0x6d, 0x61, 0x63, 0x2c, 0x20, + 0x45, 0x54, 0x48, 0x5f, 0x41, 0x4c, 0x45, 0x4e, 0x29, 0x3b, 0x00, 0x09, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x62, 0x70, 0x66, 0x5f, 0x72, + 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x28, + 0x26, 0x78, 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2c, 0x20, 0x63, + 0x74, 0x78, 0x2d, 0x3e, 0x72, 0x78, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x00, + 0x7d, 0x00, 0x63, 0x68, 0x61, 0x72, 0x00, 0x5f, 0x6c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x00, 0x2e, 0x6d, 0x61, 0x70, 0x73, 0x00, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, 0x00, 0x00, 0x9f, 0xeb, 0x01, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x2c, 0x07, 0x00, 0x00, 0x40, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x2e, 0x01, 0x00, 0x00, 0x15, 0xfc, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x2e, 0x01, 0x00, 0x00, 0x08, 0xfc, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x52, 0x01, 0x00, 0x00, 0x23, 0x00, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x06, 0x04, 0x01, 0x00, + 0x58, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xae, 0x01, 0x00, 0x00, + 0x08, 0x1c, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xae, 0x01, 0x00, 0x00, 0x13, 0x1c, 0x01, 0x00, 0x90, 0x00, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xae, 0x01, 0x00, 0x00, 0x06, 0x1c, 0x01, 0x00, + 0x98, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xd9, 0x01, 0x00, 0x00, 0x08, 0x30, 0x01, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x1d, 0x02, 0x00, 0x00, 0x22, 0x3c, 0x01, 0x00, + 0xf8, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x44, 0x02, 0x00, 0x00, + 0x32, 0x44, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x80, 0x02, 0x00, 0x00, 0x13, 0x50, 0x01, 0x00, 0x18, 0x01, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x06, 0x50, 0x01, 0x00, + 0x30, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xac, 0x02, 0x00, 0x00, + 0x2c, 0x40, 0x01, 0x00, 0x38, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xe1, 0x02, 0x00, 0x00, 0x16, 0x8c, 0x01, 0x00, 0x50, 0x01, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xe1, 0x02, 0x00, 0x00, 0x06, 0x8c, 0x01, 0x00, + 0x58, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x17, 0x03, 0x00, 0x00, + 0x0f, 0xa4, 0x01, 0x00, 0x78, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x17, 0x03, 0x00, 0x00, 0x06, 0xa4, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, 0x1c, 0xa8, 0x01, 0x00, + 0x98, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, + 0x07, 0xa8, 0x01, 0x00, 0xa8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x8d, 0x03, 0x00, 0x00, 0x0e, 0xb0, 0x01, 0x00, 0xb8, 0x01, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xa7, 0x03, 0x00, 0x00, 0x03, 0xbc, 0x01, 0x00, + 0xd8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xee, 0x03, 0x00, 0x00, + 0x02, 0xe0, 0x01, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x03, 0x04, 0x00, 0x00, 0x13, 0xec, 0x01, 0x00, 0x10, 0x02, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x07, 0xec, 0x01, 0x00, + 0x18, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x32, 0x04, 0x00, 0x00, + 0x0c, 0xf8, 0x01, 0x00, 0x20, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x32, 0x04, 0x00, 0x00, 0x14, 0xf8, 0x01, 0x00, 0x38, 0x02, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x32, 0x04, 0x00, 0x00, 0x07, 0xf8, 0x01, 0x00, + 0x40, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x4d, 0x04, 0x00, 0x00, + 0x10, 0x10, 0x02, 0x00, 0x50, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x4d, 0x04, 0x00, 0x00, 0x19, 0x10, 0x02, 0x00, 0x68, 0x02, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x4d, 0x04, 0x00, 0x00, 0x07, 0x10, 0x02, 0x00, + 0x70, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x82, 0x04, 0x00, 0x00, + 0x0c, 0x20, 0x02, 0x00, 0x78, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x82, 0x04, 0x00, 0x00, 0x1a, 0x20, 0x02, 0x00, 0xa8, 0x02, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x9e, 0x04, 0x00, 0x00, 0x1c, 0x34, 0x02, 0x00, + 0xb8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x9e, 0x04, 0x00, 0x00, + 0x11, 0x34, 0x02, 0x00, 0xc8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xbe, 0x04, 0x00, 0x00, 0x13, 0x30, 0x02, 0x00, 0xd8, 0x02, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xda, 0x04, 0x00, 0x00, 0x13, 0x48, 0x02, 0x00, + 0xf0, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xda, 0x04, 0x00, 0x00, + 0x07, 0x48, 0x02, 0x00, 0xf8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x09, 0x05, 0x00, 0x00, 0x0c, 0x54, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x09, 0x05, 0x00, 0x00, 0x14, 0x54, 0x02, 0x00, + 0x10, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x09, 0x05, 0x00, 0x00, + 0x07, 0x54, 0x02, 0x00, 0x18, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x24, 0x05, 0x00, 0x00, 0x10, 0x6c, 0x02, 0x00, 0x28, 0x03, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0x19, 0x6c, 0x02, 0x00, + 0x38, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, + 0x37, 0x6c, 0x02, 0x00, 0x48, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x24, 0x05, 0x00, 0x00, 0x07, 0x6c, 0x02, 0x00, 0x68, 0x03, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x6c, 0x05, 0x00, 0x00, 0x13, 0x7c, 0x02, 0x00, + 0x70, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x87, 0x05, 0x00, 0x00, + 0x07, 0x84, 0x02, 0x00, 0x80, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xad, 0x05, 0x00, 0x00, 0x15, 0x90, 0x02, 0x00, 0x90, 0x03, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xad, 0x05, 0x00, 0x00, 0x08, 0x90, 0x02, 0x00, + 0xa0, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xdf, 0x05, 0x00, 0x00, + 0x15, 0x9c, 0x02, 0x00, 0xb8, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xfc, 0x05, 0x00, 0x00, 0x02, 0xe0, 0x02, 0x00, + 0xf0, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x00, + 0x0e, 0xf0, 0x02, 0x00, 0x08, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x11, 0x06, 0x00, 0x00, 0x07, 0xf0, 0x02, 0x00, 0x10, 0x04, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x3b, 0x06, 0x00, 0x00, 0x13, 0x08, 0x03, 0x00, + 0x28, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x3b, 0x06, 0x00, 0x00, + 0x2a, 0x08, 0x03, 0x00, 0x30, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x67, 0x06, 0x00, 0x00, 0x0f, 0x00, 0x03, 0x00, 0x40, 0x04, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, 0x12, 0x0c, 0x03, 0x00, + 0x50, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, + 0x23, 0x0c, 0x03, 0x00, 0x58, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xb2, 0x06, 0x00, 0x00, 0x15, 0x10, 0x03, 0x00, 0x70, 0x04, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x46, 0x10, 0x03, 0x00, + 0x98, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xfa, 0x06, 0x00, 0x00, + 0x0e, 0x30, 0x03, 0x00, 0xb0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xfa, 0x06, 0x00, 0x00, 0x07, 0x30, 0x03, 0x00, 0xb8, 0x04, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0x10, 0x44, 0x03, 0x00, + 0xc0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, + 0x20, 0x44, 0x03, 0x00, 0xd8, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x24, 0x07, 0x00, 0x00, 0x07, 0x44, 0x03, 0x00, 0xe0, 0x04, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x5c, 0x07, 0x00, 0x00, 0x0f, 0x54, 0x03, 0x00, + 0xf0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x82, 0x07, 0x00, 0x00, + 0x13, 0x5c, 0x03, 0x00, 0x00, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x82, 0x07, 0x00, 0x00, 0x2a, 0x5c, 0x03, 0x00, 0x08, 0x05, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, 0x12, 0x60, 0x03, 0x00, + 0x18, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, + 0x23, 0x60, 0x03, 0x00, 0x20, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xb2, 0x06, 0x00, 0x00, 0x15, 0x64, 0x03, 0x00, 0x30, 0x05, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x46, 0x64, 0x03, 0x00, + 0x48, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xae, 0x07, 0x00, 0x00, + 0x1a, 0x70, 0x03, 0x00, 0x60, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xae, 0x07, 0x00, 0x00, 0x32, 0x70, 0x03, 0x00, 0x70, 0x05, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xe2, 0x07, 0x00, 0x00, + 0x12, 0x74, 0x03, 0x00, 0x98, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xe2, 0x07, 0x00, 0x00, 0x24, 0x74, 0x03, 0x00, 0xa0, 0x05, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x15, 0x78, 0x03, 0x00, + 0xb8, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, + 0x46, 0x78, 0x03, 0x00, 0xd8, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x08, 0x08, 0x00, 0x00, 0x18, 0xac, 0x03, 0x00, 0xf0, 0x05, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x0d, 0xac, 0x03, 0x00, + 0x10, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x39, 0x08, 0x00, 0x00, + 0x11, 0xd0, 0x03, 0x00, 0x18, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x39, 0x08, 0x00, 0x00, 0x06, 0xd0, 0x03, 0x00, 0x28, 0x06, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x64, 0x08, 0x00, 0x00, 0x19, 0xd4, 0x03, 0x00, + 0x78, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x84, 0x08, 0x00, 0x00, + 0x07, 0xe0, 0x03, 0x00, 0x90, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x92, 0x08, 0x00, 0x00, 0x11, 0xe4, 0x03, 0x00, 0x98, 0x06, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xad, 0x08, 0x00, 0x00, 0x18, 0xe8, 0x03, 0x00, + 0xa0, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xad, 0x08, 0x00, 0x00, + 0x11, 0xe8, 0x03, 0x00, 0xa8, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xcb, 0x08, 0x00, 0x00, 0x18, 0xec, 0x03, 0x00, 0xb0, 0x06, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xcb, 0x08, 0x00, 0x00, 0x11, 0xec, 0x03, 0x00, + 0xc8, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xe9, 0x08, 0x00, 0x00, + 0x0f, 0xfc, 0x03, 0x00, 0xd0, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x03, 0x09, 0x00, 0x00, 0x16, 0x00, 0x04, 0x00, 0x20, 0x07, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x1f, 0x09, 0x00, 0x00, 0x16, 0x04, 0x04, 0x00, + 0x78, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x3b, 0x09, 0x00, 0x00, 0x0d, 0x18, 0x04, 0x00, 0xb8, 0x07, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x86, 0x09, 0x00, 0x00, 0x03, 0x1c, 0x04, 0x00, + 0xe0, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00, + 0x08, 0x28, 0x04, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x97, 0x09, 0x00, 0x00, 0x15, 0x28, 0x04, 0x00, 0xf0, 0x07, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00, 0x20, 0x28, 0x04, 0x00, + 0xf8, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xb9, 0x09, 0x00, 0x00, + 0x08, 0x2c, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xb9, 0x09, 0x00, 0x00, 0x15, 0x2c, 0x04, 0x00, 0x08, 0x08, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xb9, 0x09, 0x00, 0x00, 0x20, 0x2c, 0x04, 0x00, + 0x10, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xdb, 0x09, 0x00, 0x00, + 0x08, 0x30, 0x04, 0x00, 0x18, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0xdb, 0x09, 0x00, 0x00, 0x15, 0x30, 0x04, 0x00, 0x20, 0x08, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00, 0x08, 0x28, 0x04, 0x00, + 0x28, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xfd, 0x09, 0x00, 0x00, + 0x08, 0x44, 0x04, 0x00, 0x38, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x11, 0x0a, 0x00, 0x00, 0x1e, 0x48, 0x04, 0x00, 0x40, 0x08, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x18, 0x48, 0x04, 0x00, + 0x50, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x37, 0x0a, 0x00, 0x00, + 0x04, 0x58, 0x04, 0x00, 0xb0, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, + 0x73, 0x0a, 0x00, 0x00, 0x09, 0x88, 0x04, 0x00, 0xd8, 0x08, 0x00, 0x00, + 0xce, 0x00, 0x00, 0x00, 0xb0, 0x0a, 0x00, 0x00, 0x01, 0x8c, 0x04, 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, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x28, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xa8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xb8, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x98, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x48, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xc0, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x70, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xe0, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x11, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x11, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x11, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x74, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x8c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x90, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xb0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xe0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xf0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x40, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x70, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xa0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xd0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x30, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x60, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x70, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xa0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xd0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x60, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x90, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xb0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xf0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xa0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xb0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xd0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x30, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1a, 0x1b, 0x1c, 0x1d, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, + 0x72, 0x65, 0x6c, 0x2e, 0x42, 0x54, 0x46, 0x2e, 0x65, 0x78, 0x74, 0x00, + 0x2e, 0x6d, 0x61, 0x70, 0x73, 0x00, 0x2e, 0x72, 0x65, 0x6c, 0x78, 0x64, + 0x70, 0x00, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78, + 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x2e, 0x6c, 0x6c, 0x76, + 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x69, 0x67, 0x00, 0x5f, 0x6c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, 0x78, 0x64, 0x70, 0x5f, 0x72, + 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x6e, 0x73, 0x5f, + 0x66, 0x75, 0x6e, 0x63, 0x00, 0x2e, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, + 0x00, 0x2e, 0x73, 0x79, 0x6d, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x72, 0x65, + 0x6c, 0x2e, 0x42, 0x54, 0x46, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x39, + 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x35, 0x38, 0x00, 0x4c, 0x42, 0x42, + 0x30, 0x5f, 0x35, 0x37, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x31, 0x37, + 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x35, 0x36, 0x00, 0x4c, 0x42, 0x42, + 0x30, 0x5f, 0x34, 0x36, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x33, 0x36, + 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x31, 0x36, 0x00, 0x4c, 0x42, 0x42, + 0x30, 0x5f, 0x35, 0x35, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x35, + 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x00, 0x4c, 0x42, 0x42, 0x30, + 0x5f, 0x35, 0x34, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x32, 0x33, 0x00, + 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x30, 0x00, 0x4c, 0x42, 0x42, 0x30, + 0x5f, 0x33, 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, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc4, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 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, 0x1e, 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, 0x09, 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, + 0x1a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 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, 0x43, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 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, + 0x75, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x0d, 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, 0x71, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x16, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x07, 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, + 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x03, 0x4c, 0xff, 0x6f, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 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, 0x69, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x1e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x11, 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 = 11256; 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..8bffd7f --- /dev/null +++ b/src/libknot/xdp/bpf-kernel.c @@ -0,0 +1,293 @@ +/* Copyright (C) 2022 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/tcp.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 + +#define AF_INET 2 +#define AF_INET6 10 + +/* Define maximum reasonable number of NIC queues supported. */ +#define QUEUE_MAX 256 + +/* A map of configuration options. */ +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, QUEUE_MAX); + __uint(key_size, sizeof(__u32)); /* Must be 4 bytes. */ + __uint(value_size, sizeof(knot_xdp_opts_t)); +} opts_map SEC(".maps"); + +/* A map of AF_XDP sockets. */ +struct { + __uint(type, BPF_MAP_TYPE_XSKMAP); + __uint(max_entries, QUEUE_MAX); + __uint(key_size, sizeof(__u32)); /* Must be 4 bytes. */ + __uint(value_size, sizeof(int)); +} xsks_map SEC(".maps"); + +struct ipv6_frag_hdr { + unsigned char nexthdr; + unsigned char whatever[7]; +} __attribute__((packed)); + +SEC("xdp") +int xdp_redirect_dns_func(struct xdp_md *ctx) +{ + /* Get the queue options. */ + __u32 index = ctx->rx_queue_index; + struct knot_xdp_opts *opts_ptr = bpf_map_lookup_elem(&opts_map, &index); + if (!opts_ptr) { + return XDP_ABORTED; + } + knot_xdp_opts_t opts = *opts_ptr; + + /* Check if the filter is disabled. */ + if (!(opts.flags & KNOT_XDP_FILTER_ON)) { + return XDP_PASS; + } + + /* Try to reserve space in front of the packet for additional (VLAN) data. */ + (void)bpf_xdp_adjust_meta(ctx, - (int)sizeof(struct knot_xdp_info) + - KNOT_XDP_PKT_ALIGNMENT); + + void *data = (void *)(long)ctx->data; + const void *data_end = (void *)(long)ctx->data_end; + struct knot_xdp_info *meta = (void *)(long)ctx->data_meta; + + /* Check if the meta data pointer is usable (e.g. not `tap` interface). */ + if ((void *)meta + sizeof(*meta) > data) { + meta = 0; + } + + struct ethhdr *eth_hdr = data; + const void *ip_hdr; + const struct iphdr *ip4; + const struct ipv6hdr *ip6; + const void *l4_hdr; + __u8 ipv4; + __u8 ip_proto; + __u8 fragmented = 0; + __u16 eth_type; /* In big endian. */ + + /* Parse Ethernet header. */ + if ((void *)eth_hdr + sizeof(*eth_hdr) > data_end) { + return XDP_DROP; + } + data += sizeof(*eth_hdr); + + /* Parse possible VLAN (802.1Q) header. */ + if (eth_hdr->h_proto == __constant_htons(ETH_P_8021Q)) { + if (data + sizeof(__u16) + sizeof(eth_type) > data_end) { + return XDP_DROP; + } else if (meta == 0) { /* VLAN not supported. */ + return XDP_PASS; + } + __builtin_memcpy(ð_type, data + sizeof(__u16), sizeof(eth_type)); + data += sizeof(__u16) + sizeof(eth_type); + } else { + eth_type = eth_hdr->h_proto; + } + + ip_hdr = data; + + /* Parse IPv4 or IPv6 header. */ + switch (eth_type) { + case __constant_htons(ETH_P_IP): + ip4 = ip_hdr; + if ((void *)ip4 + sizeof(*ip4) > data_end) { + return XDP_DROP; + } + if (ip4->version != 4) { + return XDP_DROP; + } + + /* Check the IP length. Cannot use strict equality due to + * Ethernet padding applied to frames shorter than 64 octects. */ + if (data_end - data < __bpf_ntohs(ip4->tot_len)) { + return XDP_DROP; + } + + if (ip4->frag_off != 0 && + ip4->frag_off != __constant_htons(IP_DF)) { + fragmented = 1; + } + ip_proto = ip4->protocol; + l4_hdr = data + ip4->ihl * 4; + ipv4 = 1; + break; + case __constant_htons(ETH_P_IPV6): + ip6 = ip_hdr; + if ((void *)ip6 + sizeof(*ip6) > data_end) { + return XDP_DROP; + } + if (ip6->version != 6) { + return XDP_DROP; + } + + /* Check the IP length. Cannot use strict equality due to + * Ethernet padding applied to frames shorter than 64 octects. */ + if (data_end - data < __bpf_ntohs(ip6->payload_len) + sizeof(*ip6)) { + 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); + } + l4_hdr = data; + ipv4 = 0; + break; + default: + /* Pass packets of possible other protocols. */ + return XDP_PASS; + } + + const struct tcphdr *tcp; + const struct udphdr *udp; + __u16 port_dest; + __u8 match = 0; + + /* Check the transport protocol. */ + switch (ip_proto) { + case IPPROTO_TCP: + /* Parse TCP header. */ + tcp = l4_hdr; + if (l4_hdr + sizeof(*tcp) > data_end) { + return XDP_DROP; + } + + port_dest = __bpf_ntohs(tcp->dest); + + if ((opts.flags & KNOT_XDP_FILTER_TCP) && + (port_dest == opts.udp_port || + ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) && + port_dest >= opts.udp_port))) { + match = 1; + } + break; + case IPPROTO_UDP: + /* Parse UDP header. */ + udp = l4_hdr; + if (l4_hdr + sizeof(*udp) > data_end) { + return XDP_DROP; + } + + /* Check the UDP length. */ + if (data_end - (void *)udp < __bpf_ntohs(udp->len)) { + return XDP_DROP; + } + + port_dest = __bpf_ntohs(udp->dest); + + if ((opts.flags & KNOT_XDP_FILTER_UDP) && + (port_dest == opts.udp_port || + ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) && + port_dest >= opts.udp_port))) { + match = 1; + } else if ((opts.flags & KNOT_XDP_FILTER_QUIC) && + (port_dest == opts.quic_port || + ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) && + port_dest >= opts.quic_port))) { + match = 1; + } + break; + default: + /* Pass packets of possible other protocols. */ + return XDP_PASS; + } + + if (!match) { + /* Pass non-matching packet. */ + return XDP_PASS; + } else if (opts.flags & KNOT_XDP_FILTER_DROP) { + /* Drop matching packet if requested. */ + return XDP_DROP; + } else if (fragmented) { + /* Drop fragmented packet. */ + return XDP_DROP; + } + + /* Take into account routing information. */ + if (opts.flags & KNOT_XDP_FILTER_ROUTE) { + struct bpf_fib_lookup fib = { + .ifindex = 1 /* Loopback. */ + }; + if (ipv4) { + fib.family = AF_INET; + fib.ipv4_src = ip4->daddr; + fib.ipv4_dst = ip4->saddr; + } else { + struct in6_addr *ipv6_src = (struct in6_addr *)fib.ipv6_src; + struct in6_addr *ipv6_dst = (struct in6_addr *)fib.ipv6_dst; + fib.family = AF_INET6; + *ipv6_src = ip6->daddr; + *ipv6_dst = ip6->saddr; + } + + const __u16 *mac_in = (const __u16 *)eth_hdr->h_dest; + const __u16 *mac_out = (const __u16 *)fib.smac; + int ret = bpf_fib_lookup(ctx, &fib, sizeof(fib), BPF_FIB_LOOKUP_DIRECT); + switch (ret) { + case BPF_FIB_LKUP_RET_SUCCESS: + /* Cross-interface answers are handled through normal stack. */ + if (mac_in[0] != mac_out[0] || + mac_in[1] != mac_out[1] || + mac_in[2] != mac_out[2]) { + return XDP_PASS; + } + + /* Store output interface index for later use with VLAN in user space. */ + if (meta != 0) { + meta->out_if_index = fib.ifindex; + } + + /* Update destination MAC for responding. */ + __builtin_memcpy(eth_hdr->h_source, fib.dmac, ETH_ALEN); + break; + case BPF_FIB_LKUP_RET_FWD_DISABLED: /* Disabled forwarding on loopback. */ + return XDP_ABORTED; + case BPF_FIB_LKUP_RET_NO_NEIGH: /* Use normal stack to obtain MAC. */ + return XDP_PASS; + default: + return XDP_DROP; + } + } + + /* Forward the packet to user space. */ + return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, 0); +} + +char _license[] SEC("license") = "GPL"; diff --git a/src/libknot/xdp/bpf-user.c b/src/libknot/xdp/bpf-user.c new file mode 100644 index 0000000..a0c8bae --- /dev/null +++ b/src/libknot/xdp/bpf-user.c @@ -0,0 +1,313 @@ +/* Copyright (C) 2022 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; + } + +#if USE_LIBXDP + ret = bpf_xdp_attach(iface->if_index, prog_fd, + overwrite ? 0 : XDP_FLAGS_UPDATE_IF_NOEXIST, NULL); +#else + ret = bpf_set_link_xdp_fd(iface->if_index, prog_fd, + overwrite ? 0 : XDP_FLAGS_UPDATE_IF_NOEXIST); +#endif + if (ret != 0) { + close(prog_fd); + } + if (ret == -EBUSY && !overwrite) { // We try accepting the present program. + uint32_t prog_id = 0; +#if USE_LIBXDP + ret = bpf_xdp_query_id(iface->if_index, 0, &prog_id); +#else + ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0); +#endif + 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; + } +} + +static void unget_bpf_maps(struct kxsk_iface *iface) +{ + if (iface->opts_map_fd >= 0) { + close(iface->opts_map_fd); + } + if (iface->xsks_map_fd >= 0) { + close(iface->xsks_map_fd); + } + iface->opts_map_fd = iface->xsks_map_fd = -1; +} + +/*! + * /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 = { 0 }; + 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, "opts_map") == 0) { + iface->opts_map_fd = fd; + continue; + } + + if (strcmp(map_info.name, "xsks_map") == 0) { + iface->xsks_map_fd = fd; + continue; + } + + close(fd); + } + + if (iface->opts_map_fd < 0 || iface->xsks_map_fd < 0) { + unget_bpf_maps(iface); + free(map_ids); + return KNOT_ENOENT; + } + + free(map_ids); + return KNOT_EOK; +} + +int kxsk_socket_start(const struct kxsk_iface *iface, knot_xdp_filter_flag_t flags, + uint16_t udp_port, uint16_t quic_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; + } + + knot_xdp_opts_t opts = { + .flags = flags | KNOT_XDP_FILTER_ON, + .udp_port = udp_port, + .quic_port = quic_port, + }; + + ret = bpf_map_update_elem(iface->opts_map_fd, &iface->if_queue, &opts, 0); + if (ret != 0) { + (void)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; + } + + knot_xdp_opts_t opts = { 0 }; + + (void)bpf_map_update_elem(iface->opts_map_fd, &iface->if_queue, &opts, 0); + (void)bpf_map_delete_elem(iface->xsks_map_fd, &iface->if_queue); +} + +int kxsk_iface_new(const char *if_name, unsigned 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->opts_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; +#if USE_LIBXDP + ret = bpf_xdp_query_id(iface->if_index, 0, &prog_id); +#else + ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0); +#endif + 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: +#if USE_LIBXDP + (void)bpf_xdp_detach(iface->if_index, 0, NULL); +#else + (void)bpf_set_link_xdp_fd(iface->if_index, -1, 0); +#endif + 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..14c54c5 --- /dev/null +++ b/src/libknot/xdp/bpf-user.h @@ -0,0 +1,139 @@ +/* Copyright (C) 2022 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 XDP socket interface. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#if USE_LIBXDP + #include <xdp/xsk.h> +#else + #include <bpf/xsk.h> +#endif + +#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. */ + unsigned if_queue; + + /*! Configuration BPF map file descriptor. */ + int opts_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; + + /*! If non-NULL, it's a mocked socket with this send function. */ + int (*send_mock)(struct knot_xdp_socket *, const knot_xdp_msg_t[], uint32_t, uint32_t *); + + /*! The kernel has to be woken up by a syscall indication. */ + bool kernel_needs_wakeup; + + /*! The limit of frame size. */ + unsigned frame_limit; + + /*! Mapping of interface indices to VLAN tags. */ + uint16_t *vlan_map; + uint16_t vlan_map_max; +}; + +/*! + * \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, unsigned 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 flags XDP filter configuration flags. + * \param udp_port UDP and/or TCP port to listen on if enabled via \a opts. + * \param quic_port QUIC/UDP port to listen on if enabled via \a opts. + * \param xsk Socket ctx. + * + * \return KNOT_E* or -errno + */ +int kxsk_socket_start(const struct kxsk_iface *iface, knot_xdp_filter_flag_t flags, + uint16_t udp_port, uint16_t quic_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..608eb20 --- /dev/null +++ b/src/libknot/xdp/eth.c @@ -0,0 +1,312 @@ +/* Copyright (C) 2023 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 <bpf/libbpf.h> +#include <errno.h> +#include <ifaddrs.h> +#include <linux/ethtool.h> +#include <linux/if_link.h> +#include <linux/if_vlan.h> +#include <linux/sockios.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "contrib/openbsd/strlcpy.h" +#include "contrib/sockaddr.h" +#include "libknot/attribute.h" +#include "libknot/endian.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 = (char *)&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_rss(const char *devname, knot_eth_rss_conf_t **rss_conf) +{ + if (devname == NULL || rss_conf == NULL) { + return KNOT_EINVAL; + } + + struct ethtool_rxfh *ctx = NULL; + knot_eth_rss_conf_t *out = NULL; + int ret = KNOT_ERROR; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return knot_map_errno(); + } + + struct ethtool_rxfh sizes = { + .cmd = ETHTOOL_GRSSH + }; + struct ifreq ifr = { + .ifr_data = (char *)&sizes + }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + ret = knot_map_errno(); + goto finish; + } + + const unsigned data_size = sizes.indir_size * sizeof(sizes.rss_config[0]) + + sizes.key_size; + + ctx = calloc(1, sizeof(*ctx) + data_size); + if (ctx == NULL) { + ret = KNOT_ENOMEM; + goto finish; + } + ctx->cmd = ETHTOOL_GRSSH; + ctx->indir_size = sizes.indir_size; + ctx->key_size = sizes.key_size; + ifr.ifr_data = (char *)ctx; + + ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + ret = knot_map_errno(); + goto finish; + } + + out = calloc(1, sizeof(*out) + data_size); + if (out == NULL) { + ret = KNOT_ENOMEM; + goto finish; + } + + out->table_size = sizes.indir_size; + out->key_size = sizes.key_size; + memcpy(out->data, ctx->rss_config, data_size); + out->mask = out->table_size - 1; +finish: + *rss_conf = out; + + free(ctx); + close(fd); + return ret; +} + +_public_ +int knot_eth_mtu(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 ifreq ifr = { 0 }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + int ret = ioctl(fd, SIOCGIFMTU, &ifr); + if (ret != 0) { + if (errno == EOPNOTSUPP) { + ret = KNOT_ENOTSUP; + } else { + ret = knot_map_errno(); + } + } else { + ret = ifr.ifr_mtu; + } + + 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_ +int knot_eth_vlans(uint16_t *vlan_map[], uint16_t *vlan_map_max) +{ + if (vlan_map == NULL || vlan_map_max == NULL) { + return KNOT_EINVAL; + } + + struct ifaddrs *ifaces = NULL; + if (getifaddrs(&ifaces) != 0) { + return knot_map_errno(); + } + + unsigned map_size = 0; + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != AF_PACKET) { + continue; + } + map_size++; + } + + uint16_t *map = calloc(sizeof(uint16_t), 1 + map_size); // Indexed from 1. + if (map == NULL) { + freeifaddrs(ifaces); + return KNOT_ENOMEM; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_PACKET) { + continue; + } + + unsigned if_index = if_nametoindex(ifa->ifa_name); + if (if_index == 0) { + close(fd); + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + struct vlan_ioctl_args ifv = { + .cmd = GET_VLAN_REALDEV_NAME_CMD + }; + strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1)); + + if (ioctl(fd, SIOCGIFVLAN, &ifv) >= 0) { + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_VID_CMD; + strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1)); + + if (ioctl(fd, SIOCGIFVLAN, &ifv) < 0) { + close(fd); + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + map[if_index] = htobe16(ifv.u.VID); + } + } + + close(fd); + freeifaddrs(ifaces); + + *vlan_map = map; + *vlan_map_max = map_size; + + return KNOT_EOK; +} + +_public_ +knot_xdp_mode_t knot_eth_xdp_mode(int if_index) +{ +#if USE_LIBXDP + struct bpf_xdp_query_opts info = { .sz = sizeof(info) }; + int ret = bpf_xdp_query(if_index, 0, &info); +#else + struct xdp_link_info info; + int ret = bpf_get_link_xdp_info(if_index, &info, sizeof(info), 0); +#endif + 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..8c5b901 --- /dev/null +++ b/src/libknot/xdp/eth.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2022 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 Ethernet device info interface. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + +#define KNOT_XDP_MAX_MTU 1790 + +/*! + * \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 Network card RSS configuration. + */ +typedef struct { + size_t table_size; /*!< Size of indirection table in four-bytes. */ + size_t key_size; /*!< Size of the RSS key in bytes. */ + uint32_t mask; /*!< Input mask for accessing the table. */ + uint32_t data[]; /*!< Serialized key and table. */ +} knot_eth_rss_conf_t; + +/*! + * \brief Get RSS configuration of a network interface. + * + * \param devname Name of the ethdev (e.g. eth1). + * \param rss_conf Output RSS configuration. Must be freed explicitly. + * + * \return KNOT_E* + */ +int knot_eth_rss(const char *devname, knot_eth_rss_conf_t **rss_conf); + +/*! + * \brief Get value of MTU setup on a network interface. + * + * \param devname Name of the ethdev (e.g. eth1). + * + * \retval < 0 KNOT_E* if error. + * \return >= 0 Interface MTU. + */ +int knot_eth_mtu(const char *devname); + +/*! + * \brief Get the corresponding network interface name for the address. + * + * \param addr Address of the interface. + * \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); + +/*! + * \brief Get the mapping of interface index to VLAN tags. + * + * \param vlan_map Output array of the mappings. + * \param vlan_map_max Maximum interface index allowed. + * + * \return KNOT_E* + */ +int knot_eth_vlans(uint16_t *vlan_map[], uint16_t *vlan_map_max); + +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 interface. + * + * \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/msg.h b/src/libknot/xdp/msg.h new file mode 100644 index 0000000..f567921 --- /dev/null +++ b/src/libknot/xdp/msg.h @@ -0,0 +1,62 @@ +/* Copyright (C) 2022 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 XDP message description. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <linux/if_ether.h> +#include <netinet/in.h> +#include <sys/uio.h> + +/*! \brief Message flags. */ +typedef enum { + KNOT_XDP_MSG_IPV6 = (1 << 0), /*!< This packet is a IPv6 (IPv4 otherwise). */ + KNOT_XDP_MSG_TCP = (1 << 1), /*!< This packet is a TCP (UDP otherwise). */ + KNOT_XDP_MSG_SYN = (1 << 2), /*!< SYN flag set (TCP only). */ + KNOT_XDP_MSG_ACK = (1 << 3), /*!< ACK flag set (TCP only). */ + KNOT_XDP_MSG_FIN = (1 << 4), /*!< FIN flag set (TCP only). */ + KNOT_XDP_MSG_RST = (1 << 5), /*!< RST flag set (TCP only). */ + KNOT_XDP_MSG_MSS = (1 << 6), /*!< MSS option in TCP header (TCP only). */ + KNOT_XDP_MSG_WSC = (1 << 7), /*!< Window Scale option in TCP header. */ + KNOT_XDP_MSG_VLAN = (1 << 8), /*!< This packet will contain VLAN header. */ +} knot_xdp_msg_flag_t; + +/*! \brief Packet description with src & dst MAC & IP addrs + DNS payload. */ +typedef struct knot_xdp_msg { + struct sockaddr_in6 ip_from; + struct sockaddr_in6 ip_to; + uint8_t eth_from[ETH_ALEN]; + uint8_t eth_to[ETH_ALEN]; + knot_xdp_msg_flag_t flags; + struct iovec payload; + uint32_t seqno; + uint32_t ackno; + uint16_t mss; + uint16_t win; + uint8_t win_scale; + uint16_t vlan_tci; +} knot_xdp_msg_t; + +/*! @} */ diff --git a/src/libknot/xdp/msg_init.h b/src/libknot/xdp/msg_init.h new file mode 100644 index 0000000..8b96129 --- /dev/null +++ b/src/libknot/xdp/msg_init.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2022 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 <string.h> + +#include "libknot/xdp/msg.h" +#include "libknot/xdp/tcp.h" +#include "libdnssec/random.h" + +inline static bool empty_msg(const knot_xdp_msg_t *msg) +{ + const unsigned tcp_flags = KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK | + KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST; + + return (msg->payload.iov_len == 0 && !(msg->flags & tcp_flags)); +} + +inline static void msg_init_base(knot_xdp_msg_t *msg, knot_xdp_msg_flag_t flags) +{ + memset(msg, 0, sizeof(*msg)); + + msg->flags = flags; +} + +inline static void msg_init(knot_xdp_msg_t *msg, knot_xdp_msg_flag_t flags) +{ + msg_init_base(msg, flags); + + if (flags & KNOT_XDP_MSG_TCP) { + msg->ackno = 0; + msg->seqno = dnssec_random_uint32_t(); + if (flags & KNOT_XDP_MSG_SYN) { + msg->flags |= KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC; + } + } +} + +inline static void msg_init_reply(knot_xdp_msg_t *msg, const knot_xdp_msg_t *query) +{ + msg_init_base(msg, query->flags & (KNOT_XDP_MSG_IPV6 | KNOT_XDP_MSG_TCP | + KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC)); + + memcpy(msg->eth_from, query->eth_to, ETH_ALEN); + memcpy(msg->eth_to, query->eth_from, ETH_ALEN); + + memcpy(&msg->ip_from, &query->ip_to, sizeof(msg->ip_from)); + memcpy(&msg->ip_to, &query->ip_from, sizeof(msg->ip_to)); + + msg->vlan_tci = query->vlan_tci; + + if (msg->flags & KNOT_XDP_MSG_TCP) { + msg->ackno = knot_tcp_next_seqno(query); + msg->seqno = query->ackno; + if (msg->seqno == 0) { + msg->seqno = dnssec_random_uint32_t(); + } + } +} diff --git a/src/libknot/xdp/protocols.h b/src/libknot/xdp/protocols.h new file mode 100644 index 0000000..ab1cff4 --- /dev/null +++ b/src/libknot/xdp/protocols.h @@ -0,0 +1,446 @@ +/* Copyright (C) 2022 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 <assert.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <string.h> + +#include "libknot/endian.h" +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/msg.h" + +/* Don't fragment flag. */ +#define IP_DF 0x4000 + +#define HDR_8021Q_LEN 4; + +/* + * Following prot_read_*() functions do not check sanity of parsed packet. + * Broken packets have to be dropped by BPF filter prior getting here. + */ + +inline static void *prot_read_udp(void *data, uint16_t *src_port, uint16_t *dst_port) +{ + const struct udphdr *udp = data; + + *src_port = udp->source; + *dst_port = udp->dest; + + return data + sizeof(*udp); +} + +enum { + PROT_TCP_OPT_ENDOP = 0, + PROT_TCP_OPT_NOOP = 1, + PROT_TCP_OPT_MSS = 2, + PROT_TCP_OPT_WSC = 3, // window scale + + PROT_TCP_OPT_LEN_MSS = 4, + PROT_TCP_OPT_LEN_WSC = 3, +}; + +inline static void *prot_read_tcp(void *data, knot_xdp_msg_t *msg, uint16_t *src_port, uint16_t *dst_port) +{ + const struct tcphdr *tcp = data; + + msg->flags |= KNOT_XDP_MSG_TCP; + + if (tcp->syn) { + msg->flags |= KNOT_XDP_MSG_SYN; + } + if (tcp->ack) { + msg->flags |= KNOT_XDP_MSG_ACK; + } + if (tcp->fin) { + msg->flags |= KNOT_XDP_MSG_FIN; + } + if (tcp->rst) { + msg->flags |= KNOT_XDP_MSG_RST; + } + + msg->seqno = be32toh(tcp->seq); + msg->ackno = be32toh(tcp->ack_seq); + msg->win = be16toh(tcp->window); + + *src_port = tcp->source; + *dst_port = tcp->dest; + + uint8_t *opts = data + sizeof(*tcp), *hdr_end = data + tcp->doff * 4; + while (opts < hdr_end) { + if (opts[0] == PROT_TCP_OPT_ENDOP || opts[0] == PROT_TCP_OPT_NOOP) { + opts++; + continue; + } + + if (opts + 1 > hdr_end || opts + opts[1] > hdr_end) { + // Malformed option. + break; + } + + if (opts[0] == PROT_TCP_OPT_MSS && opts[1] == PROT_TCP_OPT_LEN_MSS) { + msg->flags |= KNOT_XDP_MSG_MSS; + memcpy(&msg->mss, &opts[2], sizeof(msg->mss)); + msg->mss = be16toh(msg->mss); + } + + if (opts[0] == PROT_TCP_OPT_WSC && opts[1] == PROT_TCP_OPT_LEN_WSC) { + msg->flags |= KNOT_XDP_MSG_WSC; + msg->win_scale = opts[2]; + } + + opts += opts[1]; + } + + return hdr_end; +} + +inline static void *prot_read_ipv4(void *data, knot_xdp_msg_t *msg, void **data_end) +{ + const struct iphdr *ip4 = data; + + // Conditions ensured by the BPF filter. + assert(ip4->version == 4); + assert(ip4->frag_off == 0 || ip4->frag_off == __constant_htons(IP_DF)); + // IPv4 header checksum is not verified! + + struct sockaddr_in *src = (struct sockaddr_in *)&msg->ip_from; + struct sockaddr_in *dst = (struct sockaddr_in *)&msg->ip_to; + memcpy(&src->sin_addr, &ip4->saddr, sizeof(src->sin_addr)); + memcpy(&dst->sin_addr, &ip4->daddr, sizeof(dst->sin_addr)); + src->sin_family = AF_INET; + dst->sin_family = AF_INET; + + *data_end = data + be16toh(ip4->tot_len); + data += ip4->ihl * 4; + + if (ip4->protocol == IPPROTO_TCP) { + return prot_read_tcp(data, msg, &src->sin_port, &dst->sin_port); + } else { + assert(ip4->protocol == IPPROTO_UDP); + return prot_read_udp(data, &src->sin_port, &dst->sin_port); + } +} + +inline static void *prot_read_ipv6(void *data, knot_xdp_msg_t *msg, void **data_end) +{ + const struct ipv6hdr *ip6 = data; + + msg->flags |= KNOT_XDP_MSG_IPV6; + + // Conditions ensured by the BPF filter. + assert(ip6->version == 6); + + struct sockaddr_in6 *src = (struct sockaddr_in6 *)&msg->ip_from; + struct sockaddr_in6 *dst = (struct sockaddr_in6 *)&msg->ip_to; + memcpy(&src->sin6_addr, &ip6->saddr, sizeof(src->sin6_addr)); + memcpy(&dst->sin6_addr, &ip6->daddr, sizeof(dst->sin6_addr)); + src->sin6_family = AF_INET6; + dst->sin6_family = AF_INET6; + src->sin6_flowinfo = 0; + dst->sin6_flowinfo = 0; + // Scope ID is ignored. + + data += sizeof(*ip6); + *data_end = data + be16toh(ip6->payload_len); + + if (ip6->nexthdr == IPPROTO_TCP) { + return prot_read_tcp(data, msg, &src->sin6_port, &dst->sin6_port); + } else { + assert(ip6->nexthdr == IPPROTO_UDP); + return prot_read_udp(data, &src->sin6_port, &dst->sin6_port); + } +} + +inline static void *prot_read_eth(void *data, knot_xdp_msg_t *msg, void **data_end, + const uint16_t *vlan_map, unsigned vlan_map_max) +{ + const struct ethhdr *eth = data; + knot_xdp_info_t *info = data - KNOT_XDP_PKT_ALIGNMENT - sizeof(*info); + + memcpy(msg->eth_from, eth->h_source, ETH_ALEN); + memcpy(msg->eth_to, eth->h_dest, ETH_ALEN); + msg->flags = 0; + + if (eth->h_proto == __constant_htons(ETH_P_8021Q)) { + if (info->out_if_index > 0 && info->out_if_index <= vlan_map_max) { + assert(vlan_map); + msg->vlan_tci = vlan_map[info->out_if_index]; + } else { + memcpy(&msg->vlan_tci, data + sizeof(*eth), sizeof(msg->vlan_tci)); + } + data += HDR_8021Q_LEN; + eth = data; + } + + data += sizeof(*eth); + + if (eth->h_proto == __constant_htons(ETH_P_IPV6)) { + return prot_read_ipv6(data, msg, data_end); + } else { + assert(eth->h_proto == __constant_htons(ETH_P_IP)); + return prot_read_ipv4(data, msg, data_end); + } +} + +inline static size_t prot_write_hdrs_len(const knot_xdp_msg_t *msg) +{ + size_t res = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); + + if (msg->vlan_tci != 0 || msg->flags & KNOT_XDP_MSG_VLAN) { + res += HDR_8021Q_LEN; + } + + if (msg->flags & KNOT_XDP_MSG_IPV6) { + res += sizeof(struct ipv6hdr) - sizeof(struct iphdr); + } + + if (msg->flags & KNOT_XDP_MSG_TCP) { + res += sizeof(struct tcphdr) - sizeof(struct udphdr); + + if (msg->flags & KNOT_XDP_MSG_MSS) { + res += PROT_TCP_OPT_LEN_MSS; + } + if (msg->flags & KNOT_XDP_MSG_WSC) { + res += PROT_TCP_OPT_LEN_WSC + 1; // 1 == align + } + } + + return res; +} + +/* Checksum endianness implementation notes for ipv4_checksum() and checksum(). + * + * 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 little-endian (!) + */ + +inline static void checksum(uint32_t *result, const void *_data, uint32_t _data_len) +{ + assert(!(_data_len & 1)); + const uint16_t *data = _data; + uint32_t len = _data_len / 2; + while (len-- > 0) { + *result += *data++; + } +} + +inline static void checksum_uint16(uint32_t *result, uint16_t x) +{ + checksum(result, &x, sizeof(x)); +} + +inline static void checksum_payload(uint32_t *result, void *payload, size_t pay_len) +{ + if (pay_len & 1) { + ((uint8_t *)payload)[pay_len++] = 0; + } + checksum(result, payload, pay_len); +} + +inline static uint16_t checksum_finish(uint32_t result, bool nonzero) +{ + while (result > 0xffff) { + result = (result & 0xffff) + (result >> 16); + } + if (!nonzero || result != 0xffff) { + result = ~result; + } + return result; +} + +inline static void prot_write_udp(void *data, const knot_xdp_msg_t *msg, void *data_end, + uint16_t src_port, uint16_t dst_port, uint32_t chksum) +{ + struct udphdr *udp = data; + + udp->len = htobe16(data_end - data); + udp->source = src_port; + udp->dest = dst_port; + + if (msg->flags & KNOT_XDP_MSG_IPV6) { + udp->check = 0; + checksum(&chksum, &udp->len, sizeof(udp->len)); + checksum_uint16(&chksum, htobe16(IPPROTO_UDP)); + checksum_payload(&chksum, data, data_end - data); + udp->check = checksum_finish(chksum, true); + } else { + udp->check = 0; // UDP over IPv4 doesn't require checksum. + } + + assert(data + sizeof(*udp) == msg->payload.iov_base); +} + +inline static void prot_write_tcp(void *data, const knot_xdp_msg_t *msg, void *data_end, + uint16_t src_port, uint16_t dst_port, uint32_t chksum, + uint16_t mss) +{ + struct tcphdr *tcp = data; + + tcp->source = src_port; + tcp->dest = dst_port; + tcp->seq = htobe32(msg->seqno); + tcp->ack_seq = htobe32(msg->ackno); + tcp->window = htobe16(msg->win); + tcp->check = 0; // Temporarily initialize before checksum calculation. + + tcp->syn = ((msg->flags & KNOT_XDP_MSG_SYN) ? 1 : 0); + tcp->ack = ((msg->flags & KNOT_XDP_MSG_ACK) ? 1 : 0); + tcp->fin = ((msg->flags & KNOT_XDP_MSG_FIN) ? 1 : 0); + tcp->rst = ((msg->flags & KNOT_XDP_MSG_RST) ? 1 : 0); + + uint8_t *hdr_end = data + sizeof(*tcp); + if (msg->flags & KNOT_XDP_MSG_WSC) { + hdr_end[0] = PROT_TCP_OPT_WSC; + hdr_end[1] = PROT_TCP_OPT_LEN_WSC; + hdr_end[2] = msg->win_scale; + hdr_end += PROT_TCP_OPT_LEN_WSC; + *hdr_end++ = PROT_TCP_OPT_NOOP; // align + } + if (msg->flags & KNOT_XDP_MSG_MSS) { + mss = htobe16(mss); + hdr_end[0] = PROT_TCP_OPT_MSS; + hdr_end[1] = PROT_TCP_OPT_LEN_MSS; + memcpy(&hdr_end[2], &mss, sizeof(mss)); + hdr_end += PROT_TCP_OPT_LEN_MSS; + } + + tcp->psh = ((data_end - (void *)hdr_end > 0) ? 1 : 0); + tcp->doff = (hdr_end - (uint8_t *)tcp) / 4; + assert((hdr_end - (uint8_t *)tcp) % 4 == 0); + + checksum_uint16(&chksum, htobe16(IPPROTO_TCP)); + checksum_uint16(&chksum, htobe16(data_end - data)); + checksum_payload(&chksum, data, data_end - data); + tcp->check = checksum_finish(chksum, false); + + assert(hdr_end == msg->payload.iov_base); +} + +inline static uint16_t from32to16(uint32_t sum) +{ + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + return sum; +} + +inline static uint16_t ipv4_checksum(const uint16_t *ipv4_hdr) +{ + uint32_t sum32 = 0; + for (int i = 0; i < 10; ++i) { + if (i != 5) { + sum32 += ipv4_hdr[i]; + } + } + return ~from32to16(sum32); +} + +inline static void prot_write_ipv4(void *data, const knot_xdp_msg_t *msg, + void *data_end, uint16_t tcp_mss) +{ + struct iphdr *ip4 = data; + + ip4->version = 4; + ip4->ihl = sizeof(*ip4) / 4; + ip4->tos = 0; + ip4->tot_len = htobe16(data_end - data); + ip4->id = 0; + ip4->frag_off = 0; + ip4->ttl = IPDEFTTL; + ip4->protocol = ((msg->flags & KNOT_XDP_MSG_TCP) ? IPPROTO_TCP : IPPROTO_UDP); + + const struct sockaddr_in *src = (const struct sockaddr_in *)&msg->ip_from; + const struct sockaddr_in *dst = (const struct sockaddr_in *)&msg->ip_to; + memcpy(&ip4->saddr, &src->sin_addr, sizeof(src->sin_addr)); + memcpy(&ip4->daddr, &dst->sin_addr, sizeof(dst->sin_addr)); + + ip4->check = ipv4_checksum(data); + + data += sizeof(*ip4); + + if (msg->flags & KNOT_XDP_MSG_TCP) { + uint32_t chk = 0; + checksum(&chk, &src->sin_addr, sizeof(src->sin_addr)); + checksum(&chk, &dst->sin_addr, sizeof(dst->sin_addr)); + + prot_write_tcp(data, msg, data_end, src->sin_port, dst->sin_port, chk, tcp_mss); + } else { + prot_write_udp(data, msg, data_end, src->sin_port, dst->sin_port, 0); // IPv4/UDP requires no checksum + } +} + +inline static void prot_write_ipv6(void *data, const knot_xdp_msg_t *msg, + void *data_end, uint16_t tcp_mss) +{ + struct ipv6hdr *ip6 = data; + + ip6->version = 6; + ip6->priority = 0; + ip6->payload_len = htobe16(data_end - data - sizeof(*ip6)); + ip6->nexthdr = ((msg->flags & KNOT_XDP_MSG_TCP) ? IPPROTO_TCP : IPPROTO_UDP); + ip6->hop_limit = IPDEFTTL; + + memset(ip6->flow_lbl, 0, sizeof(ip6->flow_lbl)); + + const struct sockaddr_in6 *src = (const struct sockaddr_in6 *)&msg->ip_from; + const struct sockaddr_in6 *dst = (const struct sockaddr_in6 *)&msg->ip_to; + memcpy(&ip6->saddr, &src->sin6_addr, sizeof(src->sin6_addr)); + memcpy(&ip6->daddr, &dst->sin6_addr, sizeof(dst->sin6_addr)); + + data += sizeof(*ip6); + + uint32_t chk = 0; + checksum(&chk, &src->sin6_addr, sizeof(src->sin6_addr)); + checksum(&chk, &dst->sin6_addr, sizeof(dst->sin6_addr)); + + if (msg->flags & KNOT_XDP_MSG_TCP) { + prot_write_tcp(data, msg, data_end, src->sin6_port, dst->sin6_port, chk, tcp_mss); + } else { + prot_write_udp(data, msg, data_end, src->sin6_port, dst->sin6_port, chk); + } +} + +inline static void prot_write_eth(void *data, const knot_xdp_msg_t *msg, + void *data_end, uint16_t tcp_mss) +{ + struct ethhdr *eth = data; + + memcpy(eth->h_source, msg->eth_from, ETH_ALEN); + memcpy(eth->h_dest, msg->eth_to, ETH_ALEN); + + if (msg->vlan_tci != 0) { + eth->h_proto = __constant_htons(ETH_P_8021Q); + memcpy(data + sizeof(*eth), &msg->vlan_tci, sizeof(msg->vlan_tci)); + data += HDR_8021Q_LEN; + eth = data; + } + + data += sizeof(*eth); + + if (msg->flags & KNOT_XDP_MSG_IPV6) { + eth->h_proto = __constant_htons(ETH_P_IPV6); + prot_write_ipv6(data, msg, data_end, tcp_mss); + } else { + eth->h_proto = __constant_htons(ETH_P_IP); + prot_write_ipv4(data, msg, data_end, tcp_mss); + } +} diff --git a/src/libknot/xdp/quic.c b/src/libknot/xdp/quic.c new file mode 100644 index 0000000..f12a47c --- /dev/null +++ b/src/libknot/xdp/quic.c @@ -0,0 +1,1028 @@ +/* Copyright (C) 2023 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 <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_gnutls.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "libknot/xdp/quic.h" + +#include "contrib/macros.h" +#include "contrib/sockaddr.h" +#include "contrib/ucw/lists.h" +#include "libknot/endian.h" +#include "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/error.h" +#include "libknot/wire.h" + +#define SERVER_DEFAULT_SCIDLEN 18 + +#define QUIC_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3" +#define QUIC_DEFAULT_CIPHERS "-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:+CHACHA20-POLY1305:+AES-128-CCM" +#define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1" +#define QUIC_PRIORITIES "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_CIPHERS":"QUIC_DEFAULT_GROUPS + +#define XQUIC_SEND_VERSION_NEGOTIATION NGTCP2_ERR_VERSION_NEGOTIATION +#define XQUIC_SEND_RETRY NGTCP2_ERR_RETRY +#define XQUIC_SEND_STATELESS_RESET (-NGTCP2_STATELESS_RESET_TOKENLEN) + +#define TLS_CALLBACK_ERR (-1) + +typedef struct knot_quic_creds { + gnutls_certificate_credentials_t tls_cert; + gnutls_anti_replay_t tls_anti_replay; + gnutls_datum_t tls_ticket_key; + bool is_clone; +} knot_xquic_creds_t; + +typedef struct knot_quic_session { + node_t n; + gnutls_datum_t tls_session; + ngtcp2_transport_params quic_params; +} knot_xquic_session_t; + +static unsigned addr_len(const struct sockaddr_in6 *ss) +{ + return (ss->sin6_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : sizeof(struct sockaddr)); +} + +_public_ +struct knot_quic_session *knot_xquic_session_save(knot_xquic_conn_t *conn) +{ + const ngtcp2_transport_params *tmp = ngtcp2_conn_get_remote_transport_params(conn->conn); + if (tmp == NULL) { + return NULL; + } + + knot_xquic_session_t *session = calloc(1, sizeof(*session)); + if (session == NULL) { + return NULL; + } + + int ret = gnutls_session_get_data2(conn->tls_session, &session->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + free(session); + return NULL; + } + + memcpy(&session->quic_params, tmp, sizeof(session->quic_params)); + + return session; +} + +_public_ +int knot_xquic_session_load(knot_xquic_conn_t *conn, struct knot_quic_session *session) +{ + if (session == NULL) { + return KNOT_EINVAL; + } + + int ret = KNOT_EOK; + if (conn == NULL) { + goto session_free; + } + + ret = gnutls_session_set_data(conn->tls_session, session->tls_session.data, session->tls_session.size); + if (ret != KNOT_EOK) { + goto session_free; + } + + ngtcp2_conn_set_early_remote_transport_params(conn->conn, &session->quic_params); + +session_free: + gnutls_free(session->tls_session.data); + free(session); + return ret; +} + +static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time, + const gnutls_datum_t *key, + const gnutls_datum_t *data) +{ + return 0; +} + +static void tls_session_ticket_key_free(gnutls_datum_t *ticket) +{ + gnutls_memset(ticket->data, 0, ticket->size); + gnutls_free(ticket->data); +} + +static int self_signed_cert(gnutls_certificate_credentials_t tls_cert) +{ + gnutls_x509_privkey_t privkey = NULL; + gnutls_x509_crt_t cert = NULL; + char *hostname = sockaddr_hostname(); + if (hostname == NULL) { + return -ENOMEM; + } + + int ret = gnutls_x509_privkey_init(&privkey); + if (ret < 0) { + free(hostname); + return ret; + } + + uint8_t serial[16]; + gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial)); + /* clear the left-most bit to avoid signedness confusion: */ + serial[0] &= 0x8f; + +#define CHK(cmd) \ + ret = (cmd); \ + if (ret < 0) { goto finish; } + +#define now_years(years) (time(NULL) + 365 * 24 * 3600 * (years)) + + CHK(gnutls_x509_privkey_generate(privkey, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0)); + + CHK(gnutls_x509_crt_init(&cert)); + //CHK(gnutls_x509_crt_set_ca_status(cert, 0)); // TODO needed ? + CHK(gnutls_x509_crt_set_activation_time(cert, now_years(-1))); + CHK(gnutls_x509_crt_set_expiration_time(cert, now_years(20))); + CHK(gnutls_x509_crt_set_dn(cert, "CN=DoQ Self-Signed Server Certificate", NULL)); + CHK(gnutls_x509_crt_set_key(cert, privkey)); + + CHK(gnutls_x509_crt_set_serial(cert, serial, sizeof(serial))); + CHK(gnutls_x509_crt_set_subject_alt_name(cert, GNUTLS_SAN_DNSNAME, hostname, strlen(hostname), GNUTLS_FSAN_SET)); + CHK(gnutls_x509_crt_set_version(cert, 3)); + CHK(gnutls_x509_crt_sign2(cert, cert, privkey, GNUTLS_DIG_SHA256, 0)); + + ret = gnutls_certificate_set_x509_key(tls_cert, &cert, 1, privkey); + +finish: + free(hostname); + gnutls_x509_privkey_deinit(privkey); + gnutls_x509_crt_deinit(cert); + + return ret; +} + +_public_ +struct knot_quic_creds *knot_xquic_init_creds(bool server, const char *tls_cert, + const char *tls_key) +{ + knot_xquic_creds_t *creds = calloc(1, sizeof(*creds)); + if (creds == NULL) { + return NULL; + } + + int ret = gnutls_anti_replay_init(&creds->tls_anti_replay); + if (ret != GNUTLS_E_SUCCESS) { + goto fail; + } + gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func); + gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL); + + ret = gnutls_certificate_allocate_credentials(&creds->tls_cert); + if (ret != GNUTLS_E_SUCCESS) { + gnutls_anti_replay_deinit(creds->tls_anti_replay); + goto fail; + } + + ret = gnutls_certificate_set_x509_system_trust(creds->tls_cert); + if (ret < 0) { + goto fail2; + } + + if ((bool)(tls_cert == NULL) != (bool)(tls_key == NULL) || + (tls_cert != NULL && !server)) { + goto fail2; + } + if (tls_cert != NULL) { + ret = gnutls_certificate_set_x509_key_file(creds->tls_cert, tls_cert, tls_key, GNUTLS_X509_FMT_PEM); + } else if (server) { + ret = self_signed_cert(creds->tls_cert); + } + if (ret < 0) { + goto fail2; + } + + ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key); + if (ret != GNUTLS_E_SUCCESS) { + goto fail2; + } + + return creds; + +fail: + free(creds); + return NULL; +fail2: + knot_xquic_free_creds(creds); + return NULL; +} + +_public_ +void knot_xquic_free_creds(struct knot_quic_creds *creds) +{ + if (creds == NULL) { + return; + } + + if (!creds->is_clone) { + gnutls_certificate_free_credentials(creds->tls_cert); + if (creds->tls_ticket_key.data != NULL) { + tls_session_ticket_key_free(&creds->tls_ticket_key); + } + } + gnutls_anti_replay_deinit(creds->tls_anti_replay); + free(creds); +} + +#define ALPN "\03""doq" +#define ALPN_TMP1 "\07""doq-i11" +#define ALPN_TMP2 "\07""doq-i03" + +static int tls_client_hello_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + assert(htype == GNUTLS_HANDSHAKE_CLIENT_HELLO); + assert(when == GNUTLS_HOOK_POST); + + if (!incoming) { + return 0; + } + + gnutls_datum_t alpn; + int ret = gnutls_alpn_get_selected_protocol(session, &alpn); + if (ret != 0) { + return ret; + } + + const char *dq = (const char *)&ALPN[1]; + if (((unsigned int)ALPN[0] != alpn.size || + memcmp(dq, alpn.data, alpn.size) != 0) && + ((unsigned int)ALPN_TMP1[0] != alpn.size || + memcmp((const char *)&ALPN_TMP1[1], alpn.data, alpn.size) != 0) && + ((unsigned int)ALPN_TMP1[0] != alpn.size || + memcmp((const char *)&ALPN_TMP2[1], alpn.data, alpn.size) != 0)) { + return TLS_CALLBACK_ERR; + } + + return 0; +} + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + return ((knot_xquic_conn_t *)conn_ref->user_data)->conn; +} + +static int tls_init_conn_session(knot_xquic_conn_t *conn, bool server) +{ + if (gnutls_init(&conn->tls_session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | + GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_AUTO_SEND_TICKET | + GNUTLS_NO_END_OF_EARLY_DATA) != GNUTLS_E_SUCCESS) { + return TLS_CALLBACK_ERR; + } + + if (gnutls_priority_set_direct(conn->tls_session, QUIC_PRIORITIES, + NULL) != GNUTLS_E_SUCCESS) { + return TLS_CALLBACK_ERR; + } + + if (server && gnutls_session_ticket_enable_server(conn->tls_session, + &conn->xquic_table->creds->tls_ticket_key) != GNUTLS_E_SUCCESS) { + return TLS_CALLBACK_ERR; + } + + gnutls_handshake_set_hook_function(conn->tls_session, + GNUTLS_HANDSHAKE_CLIENT_HELLO, + GNUTLS_HOOK_POST, tls_client_hello_cb); + int ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session); + if (ret != 0) { + return TLS_CALLBACK_ERR; + } + + gnutls_record_set_max_early_data_size(conn->tls_session, 0xffffffffu); + + conn->conn_ref = (nc_conn_ref_placeholder_t) { + .get_conn = get_conn, + .user_data = conn + }; + + _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref), "invalid placeholder for conn_ref"); + gnutls_session_set_ptr(conn->tls_session, &conn->conn_ref); + + if (server) { + gnutls_anti_replay_enable(conn->tls_session, conn->xquic_table->creds->tls_anti_replay); + + } + if (gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE, + conn->xquic_table->creds->tls_cert) != GNUTLS_E_SUCCESS) { + return TLS_CALLBACK_ERR; + } + + + gnutls_datum_t alpn[3] = { + { + .data = (uint8_t *)(&ALPN[1]), + .size = ALPN[0], + }, + { + .data = (uint8_t *)(&ALPN_TMP1[1]), + .size = ALPN_TMP1[0], + }, + { + .data = (uint8_t *)(&ALPN_TMP2[1]), + .size = ALPN_TMP2[0], + } + }; + gnutls_alpn_set_protocols(conn->tls_session, alpn, 3, 0); + + ngtcp2_conn_set_tls_native_handle(conn->conn, conn->tls_session); + + return KNOT_EOK; +} + +static uint64_t get_timestamp(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + assert(0); + } + + return (uint64_t)ts.tv_sec * NGTCP2_SECONDS + (uint64_t)ts.tv_nsec; +} + +uint64_t xquic_conn_get_timeout(knot_xquic_conn_t *conn) +{ + // This effectively obtains the locally configured conn timeout. + // It would be possible to obey negotitated idle timeout by employing remote params, + // but this would differ per-connection and the whole idea of maintaining + // to-be-timeouted connections in simple linear list requires that + // the idle timeout is homogeneous among conns. + // Anyway, we also violate RFC9000/10.1 (Probe Timeout) for the same reason. + // TODO for the future: refactor conn table to use some tree/heap + // for to-be-timeouted conns, and use ngtcp2_conn_get_expiry() and + // ngtcp2_conn_handle_expiry() appropriately. + const ngtcp2_transport_params *params = ngtcp2_conn_get_local_transport_params(conn->conn); + + return conn->last_ts + params->max_idle_timeout; +} + +bool xquic_conn_timeout(knot_xquic_conn_t *conn, uint64_t *now) +{ + if (*now == 0) { + *now = get_timestamp(); + } + return *now > xquic_conn_get_timeout(conn); +} + +_public_ +uint32_t knot_xquic_conn_rtt(knot_xquic_conn_t *conn) +{ + ngtcp2_conn_stat stat = { 0 }; + ngtcp2_conn_get_conn_stat(conn->conn, &stat); + return stat.smoothed_rtt / 1000; // nanosec --> usec +} + +static void knot_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) +{ + (void)rand_ctx; + dnssec_random_buffer(dest, destlen); +} + +static void init_random_cid(ngtcp2_cid *cid, size_t len) +{ + if (len == 0) { + len = SERVER_DEFAULT_SCIDLEN; + } + + if (dnssec_random_buffer(cid->data, len) != DNSSEC_EOK) { + cid->datalen = 0; + } else { + cid->datalen = len; + } +} + +static bool init_unique_cid(ngtcp2_cid *cid, size_t len, knot_xquic_table_t *table) +{ + do { + if (init_random_cid(cid, len), cid->datalen == 0) { + return false; + } + } while (xquic_table_lookup(cid, table) != NULL); + return true; +} + +static int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + if (!init_unique_cid(cid, cidlen, ctx->xquic_table)) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + knot_xquic_cid_t **addto = xquic_table_insert(ctx, cid, ctx->xquic_table); + (void)addto; + + if (token != NULL && + ngtcp2_crypto_generate_stateless_reset_token( + token, (uint8_t *)ctx->xquic_table->hash_secret, + sizeof(ctx->xquic_table->hash_secret), cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, + void *user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + knot_xquic_cid_t **torem = xquic_table_lookup2(cid, ctx->xquic_table); + if (torem != NULL) { + assert((*torem)->conn == ctx); + xquic_table_rem2(torem, ctx->xquic_table); + } + + return 0; +} + +static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + assert(!ctx->handshake_done); + ctx->handshake_done = true; + + if (!ngtcp2_conn_is_server(conn)) { + return 0; + } + + gnutls_datum_t alpn; + if (gnutls_alpn_get_selected_protocol(ctx->tls_session, &alpn) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + char alpn_str[alpn.size + 1]; + alpn_str[alpn.size] = '\0'; + memcpy(alpn_str, alpn.data, alpn.size); + + if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) { + return TLS_CALLBACK_ERR; + } + + uint8_t token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]; + ngtcp2_path path = *ngtcp2_conn_get_path(ctx->conn); + uint64_t ts = get_timestamp(); + ngtcp2_ssize tokenlen = ngtcp2_crypto_generate_regular_token(token, + (uint8_t *)ctx->xquic_table->hash_secret, + sizeof(ctx->xquic_table->hash_secret), + path.remote.addr, path.remote.addrlen, ts); + if (tokenlen < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (ngtcp2_conn_submit_new_token(ctx->conn, token, tokenlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) +{ + (void)(stream_user_data); // always NULL + (void)(offset); // QUIC shall ensure that data arrive in-order + + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + int ret = knot_xquic_stream_recv_data(ctx, stream_id, data, datalen, (flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + + return ret == KNOT_EOK ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; +} + +static int acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, + void *user_data, void *stream_user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + + bool keep = !ngtcp2_conn_is_server(conn); // kxdpgun: await incomming reply after query sent&acked + + knot_xquic_stream_ack_data(ctx, stream_id, offset + datalen, keep); + + return 0; +} + +static int stream_closed(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, void *stream_user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + // NOTE possible error is stored in (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) + + bool keep = !ngtcp2_conn_is_server(conn); // kxdpgun: process incomming reply after recvd&closed + if (!keep) { + xquic_stream_free(ctx, stream_id); + } + return 0; +} + +static int recv_stateless_rst(ngtcp2_conn *conn, const ngtcp2_pkt_stateless_reset *sr, void *user_data) +{ + // NOTE server can't receive stateless resets, only client + + // ngtcp2 verified stateless reset token already + (void)(sr); + + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + assert(ctx->conn == conn); + + knot_xquic_table_rem(ctx, ctx->xquic_table); + + return 0; +} + +static int recv_stream_rst(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint64_t app_error_code, void *user_data, void *stream_user_data) +{ + (void)final_size; + return stream_closed(conn, NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET, + stream_id, app_error_code, user_data, stream_user_data); +} + +static void user_printf(void *user_data, const char *format, ...) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + if (ctx->xquic_table->log_cb != NULL) { + char buf[256]; + va_list args; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + ctx->xquic_table->log_cb(buf); + } +} + +static int conn_new(ngtcp2_conn **pconn, const ngtcp2_path *path, const ngtcp2_cid *scid, + const ngtcp2_cid *dcid, const ngtcp2_cid *odcid, uint32_t version, + uint64_t now, size_t udp_pl, uint64_t idle_timeout_ns, + void *user_data, bool server, bool retry_sent) +{ + // I. CALLBACKS + const ngtcp2_callbacks callbacks = { + ngtcp2_crypto_client_initial_cb, + ngtcp2_crypto_recv_client_initial_cb, + ngtcp2_crypto_recv_crypto_data_cb, + handshake_completed_cb, + NULL, // recv_version_negotiation not needed on server, nor kxdpgun + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + recv_stream_data, + acked_stream_data_offset_cb, + NULL, // stream_opened + stream_closed, + recv_stateless_rst, + ngtcp2_crypto_recv_retry_cb, + NULL, // extend_max_streams_bidi + NULL, // extend_max_streams_uni + knot_quic_rand_cb, + get_new_connection_id, + remove_connection_id, + ngtcp2_crypto_update_key_cb, + NULL, // path_validation, + NULL, // select_preferred_addr + recv_stream_rst, + NULL, // extend_max_remote_streams_bidi, might be useful to some allocation optimizations? + NULL, // extend_max_remote_streams_uni + NULL, // extend_max_stream_data, + NULL, // dcid_status + NULL, // handshake_confirmed + NULL, // recv_new_token + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, // recv_datagram + NULL, // ack_datagram + NULL, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + NULL, // stream_stop_sending + ngtcp2_crypto_version_negotiation_cb, + NULL, // recv_rx_key + NULL // recv_tx_key + }; + + // II. SETTINGS + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.initial_ts = now; + settings.log_printf = user_printf; + settings.max_tx_udp_payload_size = udp_pl; + settings.qlog.odcid = *odcid; + settings.handshake_timeout = idle_timeout_ns; // NOTE setting handshake timeout to idle_timeout for simplicity + settings.no_pmtud = true; + + // III. PARAMS + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + + params.initial_max_data = 786432; + params.initial_max_stream_data_bidi_local = 524288; + params.initial_max_stream_data_bidi_remote = 524288; + params.initial_max_stream_data_uni = 524288; + + // params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + // params.initial_max_stream_data_bidi_remote = config.max_stream_data_bidi_remote; + // params.initial_max_stream_data_uni = config.max_stream_data_uni; + // params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = 100; + params.initial_max_streams_uni = 3; + params.max_idle_timeout = idle_timeout_ns; + // params.stateless_reset_token_present = 1; + // params.active_connection_id_limit = 7; + if (odcid) { + params.original_dcid = *odcid; + } else { + params.original_dcid = *scid; + } + if (retry_sent) { + params.retry_scid_present = 1; + params.retry_scid = *scid; + } + if (dnssec_random_buffer(params.stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN) != DNSSEC_EOK) { + return KNOT_ERROR; + } + + if (server) { + return ngtcp2_conn_server_new(pconn, dcid, scid, path, version, &callbacks, &settings, ¶ms, NULL, user_data); + } else { + return ngtcp2_conn_client_new(pconn, dcid, scid, path, version, &callbacks, &settings, ¶ms, NULL, user_data); + } +} + +_public_ +int knot_xquic_client(knot_xquic_table_t *table, struct sockaddr_in6 *dest, + struct sockaddr_in6 *via, knot_xquic_conn_t **out_conn) +{ + ngtcp2_cid scid = { 0 }, dcid = { 0 }; + uint64_t now = get_timestamp(); + + init_random_cid(&scid, 0); + init_random_cid(&dcid, 0); + + knot_xquic_conn_t *xconn = xquic_table_add(NULL, &dcid, table); + if (xconn == NULL) { + return ENOMEM; + } + xquic_conn_mark_used(xconn, table, now); + + ngtcp2_path path; + path.remote.addr = (struct sockaddr *)dest; + path.remote.addrlen = addr_len((const struct sockaddr_in6 *)dest); + path.local.addr = (struct sockaddr *)via; + path.local.addrlen = addr_len((const struct sockaddr_in6 *)via); + + int ret = conn_new(&xconn->conn, &path, &dcid, &scid, &dcid, NGTCP2_PROTO_VER_V1, now, + table->udp_payload_limit, 5000000000L, xconn, false, false); + if (ret == KNOT_EOK) { + ret = tls_init_conn_session(xconn, false); + } + if (ret == KNOT_EOK) { + char *hostname = sockaddr_hostname(); + if (hostname == NULL) { + ret = KNOT_ENOMEM; + } else { + ret = gnutls_server_name_set(xconn->tls_session, GNUTLS_NAME_DNS, + hostname, strlen(hostname)); + free(hostname); + } + } + if (ret != KNOT_EOK) { + knot_xquic_table_rem(xconn, table); + return ret; + } + + *out_conn = xconn; + return KNOT_EOK; +} + +_public_ +int knot_xquic_handle(knot_xquic_table_t *table, knot_xdp_msg_t *msg, uint64_t idle_timeout, knot_xquic_conn_t **out_conn) +{ + *out_conn = NULL; + + ngtcp2_version_cid decoded_cids = { 0 }; + ngtcp2_cid scid = { 0 }, dcid = { 0 }, odcid = { 0 }; + uint64_t now = get_timestamp(); + int ret = ngtcp2_pkt_decode_version_cid(&decoded_cids, + msg->payload.iov_base, + msg->payload.iov_len, + SERVER_DEFAULT_SCIDLEN); + if (ret == NGTCP2_ERR_VERSION_NEGOTIATION) { + return -XQUIC_SEND_VERSION_NEGOTIATION; + } else if (ret != NGTCP2_NO_ERROR) { + return ret; + } + ngtcp2_cid_init(&dcid, decoded_cids.dcid, decoded_cids.dcidlen); + ngtcp2_cid_init(&scid, decoded_cids.scid, decoded_cids.scidlen); + + knot_xquic_conn_t *xconn = xquic_table_lookup(&dcid, table); + + if (decoded_cids.version == 0 /* short header */ && xconn == NULL) { + return KNOT_EOK; // NOOP + } + + ngtcp2_path path; + path.remote.addr = (struct sockaddr *)&msg->ip_from; + path.remote.addrlen = addr_len(&msg->ip_from); + path.local.addr = (struct sockaddr *)&msg->ip_to; + path.local.addrlen = addr_len(&msg->ip_to); + + if (xconn == NULL) { + // new conn + + ngtcp2_pkt_hd header = { 0 }; + ret = ngtcp2_accept(&header, msg->payload.iov_base, msg->payload.iov_len); + if (ret == NGTCP2_ERR_RETRY) { + return -XQUIC_SEND_RETRY; + } else if (ret != NGTCP2_NO_ERROR) { // discard packet + return KNOT_EOK; + } + + assert(header.type == NGTCP2_PKT_INITIAL); + if (header.tokenlen == 0 && xquic_require_retry(table)) { + return -XQUIC_SEND_RETRY; + } + + if (header.tokenlen > 0) { + ret = ngtcp2_crypto_verify_retry_token( + &odcid, header.token, header.tokenlen, + (const uint8_t *)table->hash_secret, sizeof(table->hash_secret), header.version, + (const struct sockaddr *)&msg->ip_from, addr_len(&msg->ip_from), + &dcid, idle_timeout, now // NOTE setting retry token validity to idle_timeout for simplicity + ); + if (ret != 0) { + return KNOT_EOK; + } + } else { + memcpy(&odcid, &dcid, sizeof(odcid)); + } + + // server chooses his CID to his liking + if (!init_unique_cid(&dcid, 0, table)) { + return KNOT_ERROR; + } + + xconn = xquic_table_add(NULL, &dcid, table); + if (xconn == NULL) { + return ENOMEM; + } + xquic_conn_mark_used(xconn, table, now); + + ret = conn_new(&xconn->conn, &path, &dcid, &scid, &odcid, decoded_cids.version, now, + table->udp_payload_limit, idle_timeout, xconn, true, header.tokenlen > 0); + if (ret >= 0) { + ret = tls_init_conn_session(xconn, true); + } + if (ret < 0) { + knot_xquic_table_rem(xconn, table); + return ret; + } + } + + ngtcp2_pkt_info pi = { .ecn = NGTCP2_ECN_NOT_ECT, }; + + ret = ngtcp2_conn_read_pkt(xconn->conn, &path, &pi, msg->payload.iov_base, msg->payload.iov_len, now); + + *out_conn = xconn; + if (ret == NGTCP2_ERR_DRAINING) { // received CONNECTION_CLOSE from the counterpart + knot_xquic_table_rem(xconn, table); + return KNOT_EOK; + } else if(ngtcp2_err_is_fatal(ret)) { // connection doomed + knot_xquic_table_rem(xconn, table); + return KNOT_ECONN; + } else if (ret != NGTCP2_NO_ERROR) { // non-fatal error, discard packet + return KNOT_EOK; + } + + xquic_conn_mark_used(xconn, table, now); + + return KNOT_EOK; +} + +static bool stream_exists(knot_xquic_conn_t *xconn, int64_t stream_id) +{ + // TRICK, we never use stream_user_data + return (ngtcp2_conn_set_stream_user_data(xconn->conn, stream_id, NULL) == NGTCP2_NO_ERROR); +} + +static int send_stream(knot_xquic_table_t *quic_table, knot_xdp_socket_t *sock, + knot_xdp_msg_t *in_msg, knot_xquic_conn_t *relay, int64_t stream_id, + uint8_t *data, size_t len, bool fin, ngtcp2_ssize *sent) +{ + (void)quic_table; + assert(stream_id >= 0 || (data == NULL && len == 0)); + + while (stream_id >= 0 && !stream_exists(relay, stream_id)) { + int64_t opened = 0; + int ret = ngtcp2_conn_open_bidi_stream(relay->conn, &opened, NULL); + if (ret != KNOT_EOK) { + return ret; + } + assert((bool)(opened == stream_id) == stream_exists(relay, stream_id)); + } + + uint32_t xdp_sent = 0; + knot_xdp_msg_t out_msg = { 0 }; + int ret = knot_xdp_reply_alloc(sock, in_msg, &out_msg); + if (ret != KNOT_EOK) { + return ret; + } + + uint32_t fl = ((stream_id >= 0 && fin) ? NGTCP2_WRITE_STREAM_FLAG_FIN : NGTCP2_WRITE_STREAM_FLAG_NONE); + ngtcp2_vec vec = { .base = data, .len = len }; + + ret = ngtcp2_conn_writev_stream(relay->conn, NULL, NULL, out_msg.payload.iov_base, out_msg.payload.iov_len, + sent, fl, stream_id, &vec, (stream_id >= 0 ? 1 : 0), get_timestamp()); + if (ret <= 0) { + knot_xdp_send_free(sock, &out_msg, 1); + return ret; + } + if (*sent < 0) { + *sent = 0; + } + + out_msg.payload.iov_len = ret; + ret = knot_xdp_send(sock, &out_msg, 1, &xdp_sent); + if (ret == KNOT_EOK) { + assert(xdp_sent == 1); + return 1; + } + return ret; +} + +static int send_special(knot_xquic_table_t *quic_table, knot_xdp_socket_t *sock, + knot_xdp_msg_t *in_msg, int handle_ret) +{ + knot_xdp_msg_t out_msg; + int ret = knot_xdp_reply_alloc(sock, in_msg, &out_msg); + if (ret != KNOT_EOK) { + return ret; + } + + uint64_t now = get_timestamp(); + ngtcp2_version_cid decoded_cids = { 0 }; + ngtcp2_cid scid = { 0 }, dcid = { 0 }; + + int dvc_ret = ngtcp2_pkt_decode_version_cid(&decoded_cids, + in_msg->payload.iov_base, + in_msg->payload.iov_len, + SERVER_DEFAULT_SCIDLEN); + + uint8_t rnd = 0; + dnssec_random_buffer(&rnd, sizeof(rnd)); + uint32_t supported_quic[1] = { NGTCP2_PROTO_VER_V1 }; + ngtcp2_cid new_dcid; + uint8_t retry_token[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN]; + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; + uint8_t sreset_rand[NGTCP2_MIN_STATELESS_RESET_RANDLEN]; + dnssec_random_buffer(sreset_rand, sizeof(sreset_rand)); + + switch (handle_ret) { + case -XQUIC_SEND_VERSION_NEGOTIATION: + if (dvc_ret != NGTCP2_ERR_VERSION_NEGOTIATION) { + return KNOT_ERROR; + } + ret = ngtcp2_pkt_write_version_negotiation( + out_msg.payload.iov_base, out_msg.payload.iov_len, + rnd, decoded_cids.scid, decoded_cids.scidlen, decoded_cids.dcid, + decoded_cids.dcidlen, supported_quic, + sizeof(supported_quic) / sizeof(*supported_quic) + ); + break; + case -XQUIC_SEND_RETRY: + ngtcp2_cid_init(&dcid, decoded_cids.dcid, decoded_cids.dcidlen); + ngtcp2_cid_init(&scid, decoded_cids.scid, decoded_cids.scidlen); + + init_random_cid(&new_dcid, 0); + + ret = ngtcp2_crypto_generate_retry_token( + retry_token, (const uint8_t *)quic_table->hash_secret, sizeof(quic_table->hash_secret), decoded_cids.version, + (const struct sockaddr *)&in_msg->ip_from, sockaddr_len((const struct sockaddr_storage *)&in_msg->ip_from), + &new_dcid, &dcid, now + ); + + if (ret >= 0) { + ret = ngtcp2_crypto_write_retry( + out_msg.payload.iov_base, out_msg.payload.iov_len, + decoded_cids.version, &scid, &new_dcid, &dcid, retry_token, ret + ); + } + break; + case -XQUIC_SEND_STATELESS_RESET: + ret = ngtcp2_pkt_write_stateless_reset( + out_msg.payload.iov_base, out_msg.payload.iov_len, + stateless_reset_token, sreset_rand, sizeof(sreset_rand) + ); + break; + default: + ret = KNOT_EINVAL; + break; + } + + if (ret < 0) { + knot_xdp_send_free(sock, &out_msg, 1); + } else { + uint32_t sent; + out_msg.payload.iov_len = ret; + ret = knot_xdp_send(sock, &out_msg, 1, &sent); + } + return ret; +} + +_public_ +int knot_xquic_send(knot_xquic_table_t *quic_table, knot_xquic_conn_t *relay, + knot_xdp_socket_t *sock, knot_xdp_msg_t *in_msg, + int handle_ret, unsigned max_msgs, bool ignore_lastbyte) +{ + if (handle_ret < 0) { + return handle_ret; + } else if (handle_ret > 0) { + return send_special(quic_table, sock, in_msg, handle_ret); + } else if (relay == NULL) { + return KNOT_EINVAL; + } else if (relay->conn == NULL) { + return KNOT_EOK; + } + + unsigned sent_msgs = 0, stream_msgs = 0; + int ret = 1; + for (int64_t si = 0; si < relay->streams_count && sent_msgs < max_msgs; /* NO INCREMENT */) { + int64_t stream_id = 4 * (relay->streams_first + si); + + ngtcp2_ssize sent = 0; + size_t uf = relay->streams[si].unsent_offset; + knot_xquic_obuf_t *uo = relay->streams[si].unsent_obuf; + if (uo == NULL) { + si++; + continue; + } + + bool fin = (((node_t *)uo->node.next)->next == NULL) && !ignore_lastbyte; + ret = send_stream(quic_table, sock, in_msg, relay, stream_id, + uo->buf + uf, uo->len - uf - (ignore_lastbyte ? 1 : 0), + fin, &sent); + if (ret < 0) { + return ret; + } + + sent_msgs++; + stream_msgs++; + if (sent > 0 && ignore_lastbyte) { + sent++; + } + if (sent > 0) { + knot_xquic_stream_mark_sent(relay, stream_id, sent); + } + + if (stream_msgs >= max_msgs / relay->streams_count) { + stream_msgs = 0; + si++; // if this stream is sending too much, give chance to other streams + } + } + + while (ret == 1) { + ngtcp2_ssize unused = 0; + ret = send_stream(quic_table, sock, in_msg, relay, -1, NULL, 0, false, &unused); + } + + return ret; +} diff --git a/src/libknot/xdp/quic.h b/src/libknot/xdp/quic.h new file mode 100644 index 0000000..943a2f3 --- /dev/null +++ b/src/libknot/xdp/quic.h @@ -0,0 +1,134 @@ +/* Copyright (C) 2022 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 General QUIC functionality. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include "libknot/xdp/quic_conn.h" +#include "libknot/xdp/xdp.h" + +struct knot_quic_creds; +struct knot_quic_session; + +/*! + * \brief Gets data needed for session resumption. + * + * \param conn QUIC connection. + * + * \return QUIC session context. + */ +struct knot_quic_session *knot_xquic_session_save(knot_xquic_conn_t *conn); + +/*! + * \brief Loads data needed for session resumption. + * + * \param conn QUIC connection. + * \param session QUIC session context. + * + * \return KNOT_E* + */ +int knot_xquic_session_load(knot_xquic_conn_t *conn, struct knot_quic_session *session); + +/*! + * \brief Init server TLS certificate for DoQ. + * + * \param server Initializing for server-side (client otherwise). + * \param tls_cert X509 certificate PEM file path/name. + * \param tls_key Key PEM file path/name. + * + * \return Initialized creds. + */ +struct knot_quic_creds *knot_xquic_init_creds(bool server, const char *tls_cert, + const char *tls_key); + +/*! + * \brief Init server TLS certificate for DoQ. + */ +void knot_xquic_free_creds(struct knot_quic_creds *creds); + +/*! + * \brief Returns timeout value for the connection. + */ +uint64_t xquic_conn_get_timeout(knot_xquic_conn_t *conn); + +/*! + * \brief Check if connection timed out due to inactivity. + * + * \param conn QUIC connection. + * \param now In/out: current monotonic time. Use zero first and reuse for + * next calls for optimization. + * + * \return True if the connection timed out idle. + */ +bool xquic_conn_timeout(knot_xquic_conn_t *conn, uint64_t *now); + +/*! + * \brief Returns measured connection RTT in usecs. + */ +uint32_t knot_xquic_conn_rtt(knot_xquic_conn_t *conn); + +/*! + * \brief Create new outgoing QUIC connection. + * + * \param table QUIC connections table to be added to. + * \param dest Destination IP address. + * \param via Source IP address. + * \param out_conn Out: new connection. + * + * \return KNOT_E* + */ +int knot_xquic_client(knot_xquic_table_t *table, struct sockaddr_in6 *dest, + struct sockaddr_in6 *via, knot_xquic_conn_t **out_conn); + +/*! + * \brief Handle incoming QUIC packet. + * + * \param table QUIC connectoins table- + * \param msg Incoming XDP packet. + * \param idle_timeout Configured idle timeout for connections (in nanoseconds). + * \param out_conn Out: QUIC connection that this packet belongs to. + * + * \return KNOT_E* + */ +int knot_xquic_handle(knot_xquic_table_t *table, knot_xdp_msg_t *msg, + uint64_t idle_timeout, knot_xquic_conn_t **out_conn); + +/*! + * \brief Send outgoing QUIC packet(s) for a connection. + * + * \param quic_table QUIC connection table. + * \param relay QUIC connection. + * \param sock XDP socket. + * \param in_msg Previous incomming packet for this connection. + * \param handle_ret Error returned from knot_xquic_handle() for incoming packet. + * \param max_msgs Maxmimum packets to be sent. + * \param ignore_lastbyte Cut off last byte of QUIC paylod. + * + * \return KNOT_E* + */ +int knot_xquic_send(knot_xquic_table_t *quic_table, knot_xquic_conn_t *relay, + knot_xdp_socket_t *sock, knot_xdp_msg_t *in_msg, + int handle_ret, unsigned max_msgs, bool ignore_lastbyte); + +/*! @} */ diff --git a/src/libknot/xdp/quic_conn.c b/src/libknot/xdp/quic_conn.c new file mode 100644 index 0000000..40a5d5b --- /dev/null +++ b/src/libknot/xdp/quic_conn.c @@ -0,0 +1,506 @@ +/* Copyright (C) 2022 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 <gnutls/gnutls.h> +#include <ngtcp2/ngtcp2.h> +#include <string.h> + +#include "libknot/xdp/quic_conn.h" + +#include "contrib/macros.h" +#include "contrib/openbsd/siphash.h" +#include "contrib/ucw/lists.h" +#include "libdnssec/random.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "libknot/xdp/quic.h" +#include "libknot/xdp/tcp_iobuf.h" +#include "libknot/wire.h" + +#define STREAM_INCR 4 // DoQ only uses client-initiated bi-directional streams, so stream IDs increment by four +#define BUCKETS_PER_CONNS 8 // Each connecion has several dCIDs, and each CID takes one hash table bucket. + +_public_ +knot_xquic_table_t *knot_xquic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs, + size_t udp_payload, struct knot_quic_creds *creds) +{ + size_t table_size = max_conns * BUCKETS_PER_CONNS; + + knot_xquic_table_t *res = calloc(1, sizeof(*res) + table_size * sizeof(res->conns[0])); + if (res == NULL) { + return NULL; + } + + res->size = table_size; + res->max_conns = max_conns; + res->ibufs_max = max_ibufs; + res->obufs_max = max_obufs; + res->udp_payload_limit = udp_payload; + init_list((list_t *)&res->timeout); + + res->creds = creds; + + res->hash_secret[0] = dnssec_random_uint64_t(); + res->hash_secret[1] = dnssec_random_uint64_t(); + res->hash_secret[2] = dnssec_random_uint64_t(); + res->hash_secret[3] = dnssec_random_uint64_t(); + + return res; +} + +_public_ +void knot_xquic_table_free(knot_xquic_table_t *table) +{ + if (table != NULL) { + knot_xquic_conn_t *c, *next; + list_t *tto = (list_t *)&table->timeout; + WALK_LIST_DELSAFE(c, next, *tto) { + knot_xquic_table_rem(c, table); + knot_xquic_cleanup(&c, 1); + } + assert(table->usage == 0); + assert(table->pointers == 0); + assert(table->obufs_size == 0); + + free(table); + } +} + +_public_ +int knot_xquic_table_sweep(knot_xquic_table_t *table, struct knot_sweep_stats *stats) +{ + uint64_t now = 0; + knot_xquic_conn_t *c, *next; + list_t *tto = (list_t *)&table->timeout; + WALK_LIST_DELSAFE(c, next, *tto) { + if (xquic_conn_timeout(c, &now)) { + knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_TIMEOUT); + knot_xquic_table_rem(c, table); + } else if (table->usage > table->max_conns) { + knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_CONN); + knot_xquic_table_rem(c, table); + // NOTE here it would be correct to send Immediate close + // with DoQ errcode DOQ_EXCESSIVE_LOAD + // nowever, we don't do this for the sake of simplicty + // it would be possible to send by using ngtcp2_conn_get_path()... + // (also applies to below case) + } else if (table->obufs_size > table->obufs_max) { + if (c->obufs_size > 0) { + knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_OBUF); + knot_xquic_table_rem(c, table); + } + } else if (table->ibufs_size > table->ibufs_max) { + if (c->ibufs_size > 0) { + knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_IBUF); + knot_xquic_table_rem(c, table); + } + } else { + break; + } + knot_xquic_cleanup(&c, 1); + } + return KNOT_EOK; +} + +static uint64_t cid2hash(const ngtcp2_cid *cid, knot_xquic_table_t *table) +{ + SIPHASH_CTX ctx; + SipHash24_Init(&ctx, (const SIPHASH_KEY *)(table->hash_secret)); + SipHash24_Update(&ctx, cid->data, MIN(cid->datalen, 8)); + uint64_t ret = SipHash24_End(&ctx); + return ret; +} + +knot_xquic_cid_t **xquic_table_insert(knot_xquic_conn_t *xconn, const ngtcp2_cid *cid, + knot_xquic_table_t *table) +{ + uint64_t hash = cid2hash(cid, table); + + knot_xquic_cid_t *cidobj = malloc(sizeof(*cidobj)); + if (cidobj == NULL) { + return NULL; + } + _Static_assert(sizeof(*cid) <= sizeof(cidobj->cid_placeholder), "insufficient placeholder for CID struct"); + memcpy(cidobj->cid_placeholder, cid, sizeof(*cid)); + cidobj->conn = xconn; + + knot_xquic_cid_t **addto = table->conns + (hash % table->size); + + cidobj->next = *addto; + *addto = cidobj; + table->pointers++; + + return addto; +} + +knot_xquic_conn_t *xquic_table_add(ngtcp2_conn *conn, const ngtcp2_cid *cid, + knot_xquic_table_t *table) +{ + knot_xquic_conn_t *xconn = calloc(1, sizeof(*xconn)); + if (xconn == NULL) { + return NULL; + } + + xconn->conn = conn; + xconn->xquic_table = table; + xconn->stream_inprocess = -1; + + knot_xquic_cid_t **addto = xquic_table_insert(xconn, cid, table); + if (addto == NULL) { + free(xconn); + return NULL; + } + table->usage++; + + return xconn; +} + +knot_xquic_cid_t **xquic_table_lookup2(const ngtcp2_cid *cid, knot_xquic_table_t *table) +{ + uint64_t hash = cid2hash(cid, table); + + knot_xquic_cid_t **res = table->conns + (hash % table->size); + while (*res != NULL && !ngtcp2_cid_eq(cid, (const ngtcp2_cid *)(*res)->cid_placeholder)) { + res = &(*res)->next; + } + return res; +} + +knot_xquic_conn_t *xquic_table_lookup(const ngtcp2_cid *cid, knot_xquic_table_t *table) +{ + knot_xquic_cid_t **pcid = xquic_table_lookup2(cid, table); + assert(pcid != NULL); + return *pcid == NULL ? NULL : (*pcid)->conn; +} + +void xquic_conn_mark_used(knot_xquic_conn_t *conn, knot_xquic_table_t *table, + uint64_t now) +{ + node_t *n = (node_t *)&conn->timeout; + list_t *l = (list_t *)&table->timeout; + if (n->next != NULL) { + rem_node(n); + } + add_tail(l, n); + conn->last_ts = now; +} + +void xquic_table_rem2(knot_xquic_cid_t **pcid, knot_xquic_table_t *table) +{ + knot_xquic_cid_t *cid = *pcid; + *pcid = cid->next; + free(cid); + table->pointers--; +} + +void xquic_stream_free(knot_xquic_conn_t *xconn, int64_t stream_id) +{ + knot_xquic_stream_ack_data(xconn, stream_id, SIZE_MAX, false); +} + +_public_ +void knot_xquic_table_rem(knot_xquic_conn_t *conn, knot_xquic_table_t *table) +{ + if (conn->streams_count == -1) { // kxdpgun special + conn->streams_count = 1; + } + for (ssize_t i = conn->streams_count - 1; i >= 0; i--) { + xquic_stream_free(conn, (i + conn->streams_first) * 4); + } + assert(conn->streams_count <= 0); + assert(conn->obufs_size == 0); + + size_t num_scid = ngtcp2_conn_get_num_scid(conn->conn); + ngtcp2_cid *scids = calloc(num_scid, sizeof(*scids)); + ngtcp2_conn_get_scid(conn->conn, scids); + + for (size_t i = 0; i < num_scid; i++) { + knot_xquic_cid_t **pcid = xquic_table_lookup2(&scids[i], table); + assert(pcid != NULL); + if (*pcid == NULL) { + continue; + } + assert((*pcid)->conn == conn); + xquic_table_rem2(pcid, table); + } + + rem_node((node_t *)&conn->timeout); + + free(scids); + + gnutls_deinit(conn->tls_session); + ngtcp2_conn_del(conn->conn); + conn->conn = NULL; + + table->usage--; +} + +_public_ +knot_xquic_stream_t *knot_xquic_conn_get_stream(knot_xquic_conn_t *xconn, + int64_t stream_id, bool create) +{ + if (stream_id % 4 != 0) { + return NULL; + } + stream_id /= 4; + + if (xconn->streams_first > stream_id) { + return NULL; + } + if (xconn->streams_count > stream_id - xconn->streams_first) { + return &xconn->streams[stream_id - xconn->streams_first]; + } + + if (create) { + size_t new_streams_count; + knot_xquic_stream_t *new_streams; + + if (xconn->streams_count == 0) { + new_streams = malloc(sizeof(new_streams[0])); + if (new_streams == NULL) { + return NULL; + } + new_streams_count = 1; + xconn->streams_first = stream_id; + } else { + new_streams_count = stream_id + 1 - xconn->streams_first; + if (new_streams_count > MAX_STREAMS_PER_CONN) { + return NULL; + } + new_streams = realloc(xconn->streams, new_streams_count * sizeof(*new_streams)); + if (new_streams == NULL) { + return NULL; + } + } + + for (knot_xquic_stream_t *si = new_streams; + si < new_streams + xconn->streams_count; si++) { + if (si->obufs_size == 0) { + init_list((list_t *)&si->outbufs); + } else { + fix_list((list_t *)&si->outbufs); + } + } + + for (knot_xquic_stream_t *si = new_streams + xconn->streams_count; + si < new_streams + new_streams_count; si++) { + memset(si, 0, sizeof(*si)); + init_list((list_t *)&si->outbufs); + } + xconn->streams = new_streams; + xconn->streams_count = new_streams_count; + + return &xconn->streams[stream_id - xconn->streams_first]; + } + return NULL; +} + +static void stream_inprocess(knot_xquic_conn_t *xconn, knot_xquic_stream_t *stream) +{ + int16_t idx = stream - xconn->streams; + assert(idx >= 0); + assert(idx < xconn->streams_count); + if (xconn->stream_inprocess < 0 || xconn->stream_inprocess > idx) { + xconn->stream_inprocess = idx; + } +} + +static void stream_outprocess(knot_xquic_conn_t *xconn, knot_xquic_stream_t *stream) +{ + if (stream != &xconn->streams[xconn->stream_inprocess]) { + return; + } + + for (int16_t idx = xconn->stream_inprocess + 1; idx < xconn->streams_count; idx++) { + stream = &xconn->streams[idx]; + if (stream->inbuf_fin != NULL) { + xconn->stream_inprocess = stream - xconn->streams; + return; + } + } + xconn->stream_inprocess = -1; +} + +int knot_xquic_stream_recv_data(knot_xquic_conn_t *xconn, int64_t stream_id, + const uint8_t *data, size_t len, bool fin) +{ + if (len == 0) { + return KNOT_EINVAL; + } + + knot_xquic_stream_t *stream = knot_xquic_conn_get_stream(xconn, stream_id, true); + if (stream == NULL) { + return KNOT_ENOENT; + } + + struct iovec in = { (void *)data, len }, *outs; + size_t outs_count; + int ret = knot_tcp_inbuf_update(&stream->inbuf, in, &outs, &outs_count, + &xconn->ibufs_size); + if (ret != KNOT_EOK || (outs_count == 0 && !fin)) { + return ret; + } + if (outs_count != 1 || !fin) { + free(outs); + return KNOT_ESEMCHECK; + } + + stream->inbuf_fin = outs; + stream_inprocess(xconn, stream); + return KNOT_EOK; +} + +_public_ +knot_xquic_stream_t *knot_xquic_stream_get_process(knot_xquic_conn_t *xconn, + int64_t *stream_id) +{ + if (xconn->stream_inprocess < 0) { + return NULL; + } + + knot_xquic_stream_t *stream = &xconn->streams[xconn->stream_inprocess]; + *stream_id = (xconn->streams_first + xconn->stream_inprocess) * 4; + stream_outprocess(xconn, stream); + return stream; +} + +_public_ +uint8_t *knot_xquic_stream_add_data(knot_xquic_conn_t *xconn, int64_t stream_id, + uint8_t *data, size_t len) +{ + knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, true); + if (s == NULL) { + return NULL; + } + + size_t prefix = sizeof(uint16_t); + + knot_xquic_obuf_t *obuf = malloc(sizeof(*obuf) + prefix + len); + if (obuf == NULL) { + return NULL; + } + + obuf->len = len + prefix; + knot_wire_write_u16(obuf->buf, len); + if (data != NULL) { + memcpy(obuf->buf + prefix, data, len); + } + + list_t *list = (list_t *)&s->outbufs; + if (EMPTY_LIST(*list)) { + s->unsent_obuf = obuf; + } + add_tail((list_t *)&s->outbufs, (node_t *)obuf); + s->obufs_size += obuf->len; + xconn->obufs_size += obuf->len; + xconn->xquic_table->obufs_size += obuf->len; + + return obuf->buf + prefix; +} + +void knot_xquic_stream_ack_data(knot_xquic_conn_t *xconn, int64_t stream_id, + size_t end_acked, bool keep_stream) +{ + knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, false); + if (s == NULL) { + return; + } + + list_t *obs = (list_t *)&s->outbufs; + + knot_xquic_obuf_t *first; + while (!EMPTY_LIST(*obs) && end_acked >= (first = HEAD(*obs))->len + s->first_offset) { + rem_node((node_t *)first); + s->obufs_size -= first->len; + xconn->obufs_size -= first->len; + xconn->xquic_table->obufs_size -= first->len; + s->first_offset += first->len; + free(first); + if (s->unsent_obuf == first) { + s->unsent_obuf = EMPTY_LIST(*obs) ? NULL : HEAD(*obs); + s->unsent_offset = 0; + } + } + + if (EMPTY_LIST(*obs) && !keep_stream) { + stream_outprocess(xconn, s); + memset(s, 0, sizeof(*s)); + while (s = &xconn->streams[0], s->inbuf.iov_len == 0 && s->inbuf_fin == NULL && s->obufs_size == 0) { + assert(xconn->streams_count > 0); + xconn->streams_count--; + + if (xconn->streams_count == 0) { + free(xconn->streams); + xconn->streams = 0; + xconn->streams_first = 0; + break; + } else { + xconn->streams_first++; + xconn->stream_inprocess--; + memmove(s, s + 1, sizeof(*s) * xconn->streams_count); + // possible realloc to shrink allocated space, but probably useless + for (knot_xquic_stream_t *si = s; si < s + xconn->streams_count; si++) { + if (si->obufs_size == 0) { + init_list((list_t *)&si->outbufs); + } else { + fix_list((list_t *)&si->outbufs); + } + } + } + } + } +} + +void knot_xquic_stream_mark_sent(knot_xquic_conn_t *xconn, int64_t stream_id, + size_t amount_sent) +{ + knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, false); + if (s == NULL) { + return; + } + + s->unsent_offset += amount_sent; + assert(s->unsent_offset <= s->unsent_obuf->len); + if (s->unsent_offset == s->unsent_obuf->len) { + s->unsent_offset = 0; + s->unsent_obuf = (knot_xquic_obuf_t *)s->unsent_obuf->node.next; + if (s->unsent_obuf->node.next == NULL) { // already behind the tail of list + s->unsent_obuf = NULL; + } + } +} + +_public_ +void knot_xquic_cleanup(knot_xquic_conn_t *conns[], size_t n_conns) +{ + for (size_t i = 0; i < n_conns; i++) { + if (conns[i] != NULL && conns[i]->conn == NULL) { + free(conns[i]); + for (size_t j = i + 1; j < n_conns; j++) { + if (conns[j] == conns[i]) { + conns[j] = NULL; + } + } + } + } +} + +bool xquic_require_retry(knot_xquic_table_t *table) +{ + (void)table; + return false; +} diff --git a/src/libknot/xdp/quic_conn.h b/src/libknot/xdp/quic_conn.h new file mode 100644 index 0000000..20ee176 --- /dev/null +++ b/src/libknot/xdp/quic_conn.h @@ -0,0 +1,314 @@ +/* Copyright (C) 2022 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 QUIC connection management. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <linux/if_ether.h> +#include <stdbool.h> +#include <stdint.h> +#include <sys/uio.h> + +#define MAX_STREAMS_PER_CONN 10 + +struct ngtcp2_cid; // declaration taken from wherever in ngtcp2 +struct knot_quic_creds; +struct knot_sweep_stats; + +// those are equivalent to contrib/ucw/lists.h , just must not be included. +typedef struct knot_xquic_ucw_node { + struct knot_xquic_ucw_node *next, *prev; +} knot_xquic_ucw_node_t; +typedef struct knot_xquic_ucw_list { + knot_xquic_ucw_node_t head, tail; +} knot_xquic_ucw_list_t; + +typedef struct { + void *get_conn; + void *user_data; +} nc_conn_ref_placeholder_t; + +typedef struct { + knot_xquic_ucw_node_t node; + size_t len; + uint8_t buf[]; +} knot_xquic_obuf_t; + +typedef struct { + struct iovec inbuf; + struct iovec *inbuf_fin; + knot_xquic_ucw_list_t outbufs; + size_t obufs_size; + + knot_xquic_obuf_t *unsent_obuf; + size_t first_offset; + size_t unsent_offset; +} knot_xquic_stream_t; + +typedef struct knot_xquic_conn { + knot_xquic_ucw_node_t timeout; // MUST be first field of the struct + uint64_t last_ts; + + nc_conn_ref_placeholder_t conn_ref; // placeholder for internal struct ngtcp2_crypto_conn_ref + + struct ngtcp2_conn *conn; + + struct gnutls_session_int *tls_session; + + knot_xquic_stream_t *streams; + int16_t streams_count; // number of allocated streams structures + int16_t stream_inprocess; // index of first stream that has complete incomming data to be processed (aka inbuf_fin) + bool handshake_done; + bool session_taken; // TODO ... ? + int64_t streams_first; // stream_id/4 of first allocated stream + size_t ibufs_size; + size_t obufs_size; + + struct knot_xquic_table *xquic_table; + + struct knot_xquic_conn *next; +} knot_xquic_conn_t; + +typedef struct knot_xquic_cid { + uint8_t cid_placeholder[32]; + knot_xquic_conn_t *conn; + struct knot_xquic_cid *next; +} knot_xquic_cid_t; + +typedef struct knot_xquic_table { + size_t size; + size_t usage; + size_t pointers; + size_t max_conns; + size_t ibufs_max; + size_t obufs_max; + size_t ibufs_size; + size_t obufs_size; + size_t udp_payload_limit; // for simplicity not distinguishing IPv4/6 + void (*log_cb)(const char *); + uint64_t hash_secret[4]; + struct knot_quic_creds *creds; + knot_xquic_ucw_list_t timeout; + knot_xquic_cid_t *conns[]; +} knot_xquic_table_t; + +/*! + * \brief Allocate QUIC connections hash table. + * + * \param max_conns Maximum nuber of connections. + * \param max_ibufs Maximum size of buffers for fragmented incomming DNS msgs. + * \param max_obufs Maximum size of buffers for un-ACKed outgoing data. + * \param udp_pl Maximum UDP payload size (both IPv4 and 6). + * \param creds QUIC crypto context.. + * + * \return Allocated table, or NULL. + */ +knot_xquic_table_t *knot_xquic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs, + size_t udp_payload, struct knot_quic_creds *creds); + +/*! + * \brief Free QUIC table including its contents. + * + * \param table Table to be freed. + */ +void knot_xquic_table_free(knot_xquic_table_t *table); + +/*! + * \brief Close timed out connections and some oldest ones if table full. + * + * \param table QUIC table to be cleaned up. + * \param stats Out: sweep statistics. + * + * \return KNOT_E* + */ +int knot_xquic_table_sweep(knot_xquic_table_t *table, struct knot_sweep_stats *stats); + +/*! + * \brief Add new connection/CID link to table. + * + * \param xconn QUIC connection linked. + * \param cid New CID to be added. + * \param table QUIC table to be modified. + * + * \return Pointer on the CID reference in table, or NULL. + */ +knot_xquic_cid_t **xquic_table_insert(knot_xquic_conn_t *xconn, + const struct ngtcp2_cid *cid, + knot_xquic_table_t *table); + +/*! + * \brief Add new connection to the table, allocating conn struct. + * + * \param conn Ngtcp2 conn struct. + * \param cid CID to be linked (usually oscid for server). + * \param table QUIC table to be modified. + * + * \return Allocated (and linked) Knot conn struct, or NULL. + */ +knot_xquic_conn_t *xquic_table_add(struct ngtcp2_conn *conn, + const struct ngtcp2_cid *cid, + knot_xquic_table_t *table); + +/*! + * \brief Lookup connection/CID link in table. + * + * \param cid CID to be searched for. + * \param table QUIC table. + * + * \return Pointer on the CID reference in table, or NULL. + */ +knot_xquic_cid_t **xquic_table_lookup2(const struct ngtcp2_cid *cid, + knot_xquic_table_t *table); + +/*! + * \brief Lookup QUIC connection in table. + * + * \param cid CID to be searched for. + * \param table QUIC table. + * + * \return Connection that the CID belongs to, or NULL. + */ +knot_xquic_conn_t *xquic_table_lookup(const struct ngtcp2_cid *cid, + knot_xquic_table_t *table); + +/*! + * \brief Put the connection on the end of timeout queue. + */ +void xquic_conn_mark_used(knot_xquic_conn_t *conn, knot_xquic_table_t *table, + uint64_t now); + +/*! + * \brief Remove connection/CID link from table. + * + * \param pcid CID to be removed. + * \param table QUIC table. + */ +void xquic_table_rem2(knot_xquic_cid_t **pcid, knot_xquic_table_t *table); + +/*! + * \brief Remove specified stream from QUIC connection, freeing all buffers. + * + * \param xconn QUIC connection to remove from. + * \param stream_id Stream QUIC ID. + */ +void xquic_stream_free(knot_xquic_conn_t *xconn, int64_t stream_id); + +/*! + * \brief Remove and deinitialize connection completely. + * + * \param conn Connection to be removed. + * \param table Table to remove from. + */ +void knot_xquic_table_rem(knot_xquic_conn_t *conn, knot_xquic_table_t *table); + +/*! + * \brief Fetch or initialize a QUIC stream. + * + * \param xconn QUIC connection. + * \param stream_id Stream QUIC ID. + * \param create Trigger stream creation if not exists. + * + * \return Stream or NULL. + */ +knot_xquic_stream_t *knot_xquic_conn_get_stream(knot_xquic_conn_t *xconn, + int64_t stream_id, bool create); + +/*! + * \brief Process incomming stream data to stream structure. + * + * \param xconn QUIC connection that has received data. + * \param stream_id Stream QUIC ID of the incomming data. + * \param data Incomming payload data. + * \param len Incomming payload data length. + * \param fin FIN flag set for incomming data. + * + * \return KNOT_E* + */ +int knot_xquic_stream_recv_data(knot_xquic_conn_t *xconn, int64_t stream_id, + const uint8_t *data, size_t len, bool fin); + +/*! + * \brief Get next stream which has pending incomming data to be processed. + * + * \param xconn QUIC connection. + * \param stream_id Out: strem QUIC ID of the returned stream. + * + * \return Stream with incomming data. + */ +knot_xquic_stream_t *knot_xquic_stream_get_process(knot_xquic_conn_t *xconn, + int64_t *stream_id); + +/*! + * \brief Add outgiong data to the stream for sending. + * + * \param xconn QUIC connection that shall send data. + * \param stream_id Stream ID for outgoing data. + * \param data Data payload. + * \param len Data payload length. + * + * \return NULL if error, or pinter at the data in outgiong buffer. + */ +uint8_t *knot_xquic_stream_add_data(knot_xquic_conn_t *xconn, int64_t stream_id, + uint8_t *data, size_t len); + +/*! + * \brief Mark outgiong data as acknowledged after ACK received. + * + * \param xconn QUIC connection that received ACK. + * \param stream_id Stream ID of ACKed data. + * \param end_acked Offset of ACKed data + ACKed length. + * \param keep_stream Don't free the stream even when ACKed all outgoing data. + */ +void knot_xquic_stream_ack_data(knot_xquic_conn_t *xconn, int64_t stream_id, + size_t end_acked, bool keep_stream); + +/*! + * \brief Mark outgoing data as sent. + * + * \param xconn QUIC connection that sent data. + * \param stream_id Stream ID of sent data. + * \param amount_sent Length of sent data. + */ +void knot_xquic_stream_mark_sent(knot_xquic_conn_t *xconn, int64_t stream_id, + size_t amount_sent); + +/*! + * \brief Free rest of resources of closed conns. + * + * \param conns Array with recently used conns (possibly NULLs). + * \param n_conns Size of the array. + */ +void knot_xquic_cleanup(knot_xquic_conn_t *conns[], size_t n_conns); + +/*! + * \brief Toggle sending Retry packet as a reaction to Initial packet of new connection. + * + * \param table Connection table. + * + * \return True if instead of continuing handshake, Retry packet shall be sent + * to verify counterpart's address. + */ +bool xquic_require_retry(knot_xquic_table_t *table); + +/*! @} */ diff --git a/src/libknot/xdp/tcp.c b/src/libknot/xdp/tcp.c new file mode 100644 index 0000000..752e9ef --- /dev/null +++ b/src/libknot/xdp/tcp.c @@ -0,0 +1,729 @@ +/* Copyright (C) 2022 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 <limits.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "libknot/xdp/tcp.h" +#include "libknot/xdp/tcp_iobuf.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "libdnssec/random.h" +#include "contrib/macros.h" +#include "contrib/openbsd/siphash.h" +#include "contrib/ucw/lists.h" + +static uint32_t get_timestamp(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + uint64_t res = (uint64_t)t.tv_sec * 1000000; + res += (uint64_t)t.tv_nsec / 1000; + return res & 0xffffffff; // overflow does not matter since we are working with differences +} + +static size_t sockaddr_data_len(const struct sockaddr_in6 *rem, const struct sockaddr_in6 *loc) +{ + assert(rem->sin6_family == loc->sin6_family); + if (rem->sin6_family == AF_INET) { + return offsetof(struct sockaddr_in, sin_zero); + } else { + assert(rem->sin6_family == AF_INET6); + return offsetof(struct sockaddr_in6, sin6_scope_id); + } +} + +static uint64_t hash_four_tuple(const struct sockaddr_in6 *rem, const struct sockaddr_in6 *loc, + knot_tcp_table_t *table) +{ + size_t socka_data_len = sockaddr_data_len(rem, loc); + SIPHASH_CTX ctx; + SipHash24_Init(&ctx, (const SIPHASH_KEY *)(table->hash_secret)); + SipHash24_Update(&ctx, rem, socka_data_len); + SipHash24_Update(&ctx, loc, socka_data_len); + return SipHash24_End(&ctx); +} + +static list_t *tcp_table_timeout(knot_tcp_table_t *table) +{ + return (list_t *)&table->conns[table->size]; +} + +static node_t *tcp_conn_node(knot_tcp_conn_t *conn) +{ + return (node_t *)&conn->list_node_placeholder; +} + +static void next_node_ptr(knot_tcp_conn_t **ptr) +{ + if (*ptr != NULL) { + *ptr = (*ptr)->list_node_placeholder.list_node_next; + if ((*ptr)->list_node_placeholder.list_node_next == NULL) { // detected tail of list + *ptr = NULL; + } + } +} + +static void next_ptr_ibuf(knot_tcp_conn_t **ptr) +{ + do { + next_node_ptr(ptr); + } while (*ptr != NULL && (*ptr)->inbuf.iov_len == 0); +} + +static void next_ptr_obuf(knot_tcp_conn_t **ptr) +{ + do { + next_node_ptr(ptr); + } while (*ptr != NULL && knot_tcp_outbufs_usage((*ptr)->outbufs) == 0); +} + +_public_ +knot_tcp_table_t *knot_tcp_table_new(size_t size, knot_tcp_table_t *secret_share) +{ + knot_tcp_table_t *table = calloc(1, sizeof(*table) + sizeof(list_t) + + size * sizeof(table->conns[0])); + if (table == NULL) { + return table; + } + + table->size = size; + init_list(tcp_table_timeout(table)); + + assert(sizeof(table->hash_secret) == sizeof(SIPHASH_KEY)); + if (secret_share == NULL) { + table->hash_secret[0] = dnssec_random_uint64_t(); + table->hash_secret[1] = dnssec_random_uint64_t(); + } else { + table->hash_secret[0] = secret_share->hash_secret[0]; + table->hash_secret[1] = secret_share->hash_secret[1]; + } + + return table; +} + +static void del_conn(knot_tcp_conn_t *conn) +{ + if (conn != NULL) { + free(conn->inbuf.iov_base); + while (conn->outbufs != NULL) { + struct knot_tcp_outbuf *next = conn->outbufs->next; + free(conn->outbufs); + conn->outbufs = next; + } + free(conn); + } +} + +_public_ +void knot_tcp_table_free(knot_tcp_table_t *table) +{ + if (table != NULL) { + knot_tcp_conn_t *conn, *next; + WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(table)) { + del_conn(conn); + } + free(table); + } +} + +static knot_tcp_conn_t **tcp_table_lookup(const struct sockaddr_in6 *rem, + const struct sockaddr_in6 *loc, + uint64_t *hash, knot_tcp_table_t *table) +{ + if (*hash == 0) { + *hash = hash_four_tuple(rem, loc, table); + } + size_t sdl = sockaddr_data_len(rem, loc); + knot_tcp_conn_t **res = table->conns + (*hash % table->size); + while (*res != NULL) { + if (memcmp(&(*res)->ip_rem, rem, sdl) == 0 && + memcmp(&(*res)->ip_loc, loc, sdl) == 0) { + break; + } + res = &(*res)->next; + } + return res; +} + +static knot_tcp_conn_t **tcp_table_re_lookup(knot_tcp_conn_t *conn, + knot_tcp_table_t *table) +{ + uint64_t unused_hash = 0; + knot_tcp_conn_t **res = tcp_table_lookup(&conn->ip_rem, &conn->ip_loc, + &unused_hash, table); + assert(*res == conn); + return res; +} + +static void rem_align_pointers(knot_tcp_conn_t *to_rem, knot_tcp_table_t *table) +{ + if (to_rem == table->next_close) { + next_node_ptr(&table->next_close); + } + if (to_rem == table->next_ibuf) { + next_ptr_ibuf(&table->next_ibuf); + } + if (to_rem == table->next_obuf) { + next_ptr_obuf(&table->next_obuf); + } + if (to_rem == table->next_resend) { + next_ptr_obuf(&table->next_resend); + } +} + +static void tcp_table_remove_conn(knot_tcp_conn_t **todel) +{ + rem_node(tcp_conn_node(*todel)); // remove from timeout double-linked list + *todel = (*todel)->next; // remove from conn-table linked list +} + +static void tcp_table_remove(knot_tcp_conn_t **todel, knot_tcp_table_t *table) +{ + assert(table->usage > 0); + rem_align_pointers(*todel, table); + table->inbufs_total -= (*todel)->inbuf.iov_len; + table->outbufs_total -= knot_tcp_outbufs_usage((*todel)->outbufs); + tcp_table_remove_conn(todel); + table->usage--; +} + +static void conn_init_from_msg(knot_tcp_conn_t *conn, knot_xdp_msg_t *msg) +{ + memcpy(&conn->ip_rem, &msg->ip_from, sizeof(conn->ip_rem)); + memcpy(&conn->ip_loc, &msg->ip_to, sizeof(conn->ip_loc)); + + memcpy(&conn->last_eth_rem, &msg->eth_from, sizeof(conn->last_eth_rem)); + memcpy(&conn->last_eth_loc, &msg->eth_to, sizeof(conn->last_eth_loc)); + + conn->seqno = msg->seqno; + conn->ackno = msg->ackno; + conn->acked = msg->ackno; + + conn->last_active = get_timestamp(); + conn->state = XDP_TCP_NORMAL; + conn->establish_rtt = 0; + + memset(&conn->inbuf, 0, sizeof(conn->inbuf)); + memset(&conn->outbufs, 0, sizeof(conn->outbufs)); +} + +static void tcp_table_insert(knot_tcp_conn_t *conn, uint64_t hash, + knot_tcp_table_t *table) +{ + knot_tcp_conn_t **addto = table->conns + (hash % table->size); + add_tail(tcp_table_timeout(table), tcp_conn_node(conn)); + if (table->next_close == NULL) { + table->next_close = conn; + } + conn->next = *addto; + *addto = conn; + table->usage++; +} + +// WARNING you shall ensure that it's not in the table already! +static int tcp_table_add(knot_xdp_msg_t *msg, uint64_t hash, knot_tcp_table_t *table, + knot_tcp_conn_t **res) +{ + knot_tcp_conn_t *c = malloc(sizeof(*c)); + if (c == NULL) { + return KNOT_ENOMEM; + } + conn_init_from_msg(c, msg); + tcp_table_insert(c, hash, table); + *res = c; + return KNOT_EOK; +} + +static bool check_seq_ack(const knot_xdp_msg_t *msg, const knot_tcp_conn_t *conn) +{ + if (conn == NULL || conn->seqno != msg->seqno) { + return false; + } + + if (conn->acked <= conn->ackno) { // ackno does not wrap around uint32 + return (msg->ackno >= conn->acked && msg->ackno <= conn->ackno); + } else { // this is more tricky + return (msg->ackno >= conn->acked || msg->ackno <= conn->ackno); + } +} + +static void conn_update(knot_tcp_conn_t *conn, const knot_xdp_msg_t *msg) +{ + conn->seqno = knot_tcp_next_seqno(msg); + memcpy(conn->last_eth_rem, msg->eth_from, sizeof(conn->last_eth_rem)); + memcpy(conn->last_eth_loc, msg->eth_to, sizeof(conn->last_eth_loc)); + conn->window_size = (uint32_t)msg->win * (1LU << conn->window_scale); + + uint32_t now = get_timestamp(); + if (conn->establish_rtt == 0 && conn->last_active != 0) { + conn->establish_rtt = now - conn->last_active; + } + conn->last_active = now; +} + +_public_ +int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count, + knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, + knot_tcp_ignore_t ignore) +{ + if (msg_count == 0) { + return KNOT_EOK; + } + if (relays == NULL || msgs == NULL || tcp_table == NULL) { + return KNOT_EINVAL; + } + memset(relays, 0, msg_count * sizeof(*relays)); + + knot_tcp_relay_t *relay = relays; + int ret = KNOT_EOK; + + for (knot_xdp_msg_t *msg = msgs; msg != msgs + msg_count && ret == KNOT_EOK; msg++) { + if (!(msg->flags & KNOT_XDP_MSG_TCP)) { + continue; + } + + uint64_t conn_hash = 0; + knot_tcp_conn_t **pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, + &conn_hash, tcp_table); + knot_tcp_conn_t *conn = *pconn; + bool seq_ack_match = check_seq_ack(msg, conn); + if (seq_ack_match) { + assert(conn->mss != 0); + conn_update(conn, msg); + + rem_align_pointers(conn, tcp_table); + rem_node(tcp_conn_node(conn)); + add_tail(tcp_table_timeout(tcp_table), tcp_conn_node(conn)); + + if (msg->flags & KNOT_XDP_MSG_ACK) { + conn->acked = msg->ackno; + knot_tcp_outbufs_ack(&conn->outbufs, msg->ackno, &tcp_table->outbufs_total); + } + } + + relay->msg = msg; + relay->conn = conn; + + // process incoming data + if (seq_ack_match && (msg->flags & KNOT_XDP_MSG_ACK) && msg->payload.iov_len > 0) { + if (!(ignore & XDP_TCP_IGNORE_DATA_ACK)) { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } + ret = knot_tcp_inbuf_update(&conn->inbuf, msg->payload, &relay->inbufs, + &relay->inbufs_count, &tcp_table->inbufs_total); + if (ret != KNOT_EOK) { + break; + } + if (conn->inbuf.iov_len > 0 && tcp_table->next_ibuf == NULL) { + tcp_table->next_ibuf = conn; + } + } + + // process TCP connection state + switch (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK | + KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST)) { + case KNOT_XDP_MSG_SYN: + case (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK): + if (conn == NULL) { + bool synack = (msg->flags & KNOT_XDP_MSG_ACK); + + knot_tcp_table_t *add_table = tcp_table; + if (syn_table != NULL && !synack) { + add_table = syn_table; + if (*tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table) != NULL) { + break; + } + } + + ret = tcp_table_add(msg, conn_hash, add_table, &relay->conn); + if (ret == KNOT_EOK) { + relay->action = synack ? XDP_TCP_ESTABLISH : XDP_TCP_SYN; + if (!(ignore & XDP_TCP_IGNORE_ESTABLISH)) { + relay->auto_answer = synack ? KNOT_XDP_MSG_ACK : (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK); + } + + conn = relay->conn; + conn->state = synack ? XDP_TCP_NORMAL: XDP_TCP_ESTABLISHING; + conn->mss = MAX(msg->mss, 536); // minimal MSS, most importantly not zero! + conn->window_scale = msg->win_scale; + conn_update(conn, msg); + if (!synack) { + conn->acked = dnssec_random_uint32_t(); + conn->ackno = conn->acked; + } + } + } else { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } + break; + case KNOT_XDP_MSG_ACK: + if (!seq_ack_match) { + if (syn_table != NULL && msg->payload.iov_len == 0 && + (pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table)) != NULL && + (conn = *pconn) != NULL && check_seq_ack(msg, conn)) { + // move conn from syn_table to tcp_table + tcp_table_remove(pconn, syn_table); + tcp_table_insert(conn, conn_hash, tcp_table); + relay->conn = conn; + relay->action = XDP_TCP_ESTABLISH; + conn->state = XDP_TCP_NORMAL; + conn_update(conn, msg); + } + } else { + switch (conn->state) { + case XDP_TCP_NORMAL: + case XDP_TCP_CLOSING1: // just a mess, ignore + break; + case XDP_TCP_ESTABLISHING: + conn->state = XDP_TCP_NORMAL; + relay->action = XDP_TCP_ESTABLISH; + break; + case XDP_TCP_CLOSING2: + if (msg->payload.iov_len == 0) { // otherwise ignore close + tcp_table_remove(pconn, tcp_table); + relay->answer = XDP_TCP_FREE; + } + break; + } + } + break; + case (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK): + if (ignore & XDP_TCP_IGNORE_FIN) { + break; + } + if (!seq_ack_match) { + if (conn != NULL) { + relay->auto_answer = KNOT_XDP_MSG_RST; + relay->auto_seqno = msg->ackno; + } // else ignore. It would be better and possible, but no big value for the price of CPU. + } else { + if (conn->state == XDP_TCP_CLOSING1) { + relay->action = XDP_TCP_CLOSE; + relay->auto_answer = KNOT_XDP_MSG_ACK; + relay->answer = XDP_TCP_FREE; + tcp_table_remove(pconn, tcp_table); + } else if (msg->payload.iov_len == 0) { // otherwise ignore FIN + relay->action = XDP_TCP_CLOSE; + relay->auto_answer = KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK; + conn->state = XDP_TCP_CLOSING2; + } + } + break; + case KNOT_XDP_MSG_RST: + if (conn != NULL && msg->seqno == conn->seqno) { + relay->action = XDP_TCP_RESET; + tcp_table_remove(pconn, tcp_table); + relay->answer = XDP_TCP_FREE; + } else if (conn != NULL) { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } + break; + default: + break; + } + + if (!knot_tcp_relay_empty(relay)) { + relay++; + } + } + + return ret; +} + +_public_ +int knot_tcp_reply_data(knot_tcp_relay_t *relay, knot_tcp_table_t *tcp_table, + bool ignore_lastbyte, uint8_t *data, uint32_t len) +{ + if (relay == NULL || tcp_table == NULL || relay->conn == NULL) { + return KNOT_EINVAL; + } + int ret = knot_tcp_outbufs_add(&relay->conn->outbufs, data, len, ignore_lastbyte, + relay->conn->mss, &tcp_table->outbufs_total); + + if (tcp_table->next_obuf == NULL && knot_tcp_outbufs_usage(relay->conn->outbufs) > 0) { + tcp_table->next_obuf = relay->conn; + } + if (tcp_table->next_resend == NULL && knot_tcp_outbufs_usage(relay->conn->outbufs) > 0) { + tcp_table->next_resend = relay->conn; + } + return ret; +} + +static knot_xdp_msg_t *first_msg(knot_xdp_msg_t *msgs, uint32_t n_msgs) +{ + memset(msgs, 0, n_msgs * sizeof(*msgs)); + return msgs - 1; // will be incremented just before first use +} + +static int send_msgs(knot_xdp_msg_t *msgs, uint32_t n_msgs, knot_xdp_socket_t *socket) +{ + assert(socket); + assert(msgs); + + if (n_msgs > 0) { + uint32_t unused; + return knot_xdp_send(socket, msgs, n_msgs, &unused); + } + + return KNOT_EOK; +} + +static void msg_init_from_conn(knot_xdp_msg_t *msg, knot_tcp_conn_t *conn) +{ + memcpy( msg->eth_from, conn->last_eth_loc, sizeof(msg->eth_from)); + memcpy( msg->eth_to, conn->last_eth_rem, sizeof(msg->eth_to)); + memcpy(&msg->ip_from, &conn->ip_loc, sizeof(msg->ip_from)); + memcpy(&msg->ip_to, &conn->ip_rem, sizeof(msg->ip_to)); + + msg->ackno = conn->seqno; + msg->seqno = conn->ackno; + + msg->payload.iov_len = 0; + + msg->win_scale = 14; // maximum possible + msg->win = 0xffff; +} + +static int next_msg(knot_xdp_msg_t *msgs, uint32_t n_msgs, knot_xdp_msg_t **cur, + knot_xdp_socket_t *socket, knot_tcp_relay_t *rl) +{ + (*cur)++; + if (*cur - msgs >= n_msgs) { + int ret = send_msgs(msgs, n_msgs, socket); + if (ret != KNOT_EOK) { + return ret; + } + *cur = first_msg(msgs, n_msgs); + (*cur)++; + } + + knot_xdp_msg_t *msg = *cur; + + knot_xdp_msg_flag_t fl = KNOT_XDP_MSG_TCP; + if (rl->conn->ip_loc.sin6_family == AF_INET6) { + fl |= KNOT_XDP_MSG_IPV6; + } + if (rl->conn->state == XDP_TCP_ESTABLISHING) { + fl |= KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC; + } + + int ret = knot_xdp_send_alloc(socket, fl, msg); + if (ret != KNOT_EOK) { + return ret; + } + + msg_init_from_conn(msg, rl->conn); + + return ret; +} + +_public_ +int knot_tcp_send(knot_xdp_socket_t *socket, knot_tcp_relay_t relays[], + uint32_t relay_count, uint32_t max_at_once) +{ + if (relay_count == 0) { + return KNOT_EOK; + } + if (socket == NULL || relays == NULL) { + return KNOT_EINVAL; + } + + knot_xdp_msg_t msgs[max_at_once], *first = first_msg(msgs, max_at_once), *msg = first; + + for (uint32_t i = 0; i < relay_count; i++) { + knot_tcp_relay_t *rl = &relays[i]; + +#define NEXT_MSG { \ + int ret = next_msg(msgs, max_at_once, &msg, socket, rl); \ + if (ret != KNOT_EOK) { return ret; } \ +} + + if (rl->auto_answer != 0) { + NEXT_MSG + msg->flags |= rl->auto_answer; + if (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_FIN)) { + rl->conn->ackno++; + } + if (rl->auto_answer == KNOT_XDP_MSG_RST) { + msg->seqno = rl->auto_seqno; + } + } + + switch (rl->answer & 0x0f) { + case XDP_TCP_ESTABLISH: + NEXT_MSG + msg->flags |= KNOT_XDP_MSG_SYN; + rl->conn->ackno++; + break; + case XDP_TCP_CLOSE: + NEXT_MSG + msg->flags |= (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK); + rl->conn->ackno++; + rl->conn->state = XDP_TCP_CLOSING1; + break; + case XDP_TCP_RESET: + NEXT_MSG + msg->flags |= KNOT_XDP_MSG_RST; + break; + case XDP_TCP_NOOP: + default: + break; + } + + size_t can_data = 0; + knot_tcp_outbuf_t *ob; + if (rl->conn != NULL) { + knot_tcp_outbufs_can_send(rl->conn->outbufs, rl->conn->window_size, + rl->answer == XDP_TCP_RESEND, &ob, &can_data); + } + while (can_data > 0) { + NEXT_MSG + msg->flags |= KNOT_XDP_MSG_ACK; + msg->payload.iov_len = ob->len; + memcpy(msg->payload.iov_base, ob->bytes, ob->len); + + if (!ob->sent) { + assert(rl->conn->ackno == msg->seqno); + rl->conn->ackno += msg->payload.iov_len; + } else { + msg->seqno = ob->seqno; + } + + ob->sent = true; + ob->seqno = msg->seqno; + + can_data--; + ob = ob->next; + } +#undef NEXT_MSG + } + + return send_msgs(msgs, msg - first, socket); +} + +static void sweep_reset(knot_tcp_table_t *tcp_table, knot_tcp_relay_t *rl, + ssize_t *free_conns, ssize_t *free_inbuf, ssize_t *free_outbuf, + knot_sweep_stats_t *stats, knot_sweep_counter_t counter) +{ + rl->answer = XDP_TCP_RESET | XDP_TCP_FREE; + tcp_table_remove(tcp_table_re_lookup(rl->conn, tcp_table), tcp_table); // also updates tcp_table->next_* + + *free_conns -= 1; + *free_inbuf -= rl->conn->inbuf.iov_len; + *free_outbuf -= knot_tcp_outbufs_usage(rl->conn->outbufs); + + knot_sweep_stats_incr(stats, counter); +} + +_public_ +int knot_tcp_sweep(knot_tcp_table_t *tcp_table, + uint32_t close_timeout, uint32_t reset_timeout, + uint32_t resend_timeout, uint32_t limit_conn_count, + size_t limit_ibuf_size, size_t limit_obuf_size, + knot_tcp_relay_t *relays, uint32_t max_relays, + struct knot_sweep_stats *stats) +{ + if (tcp_table == NULL || relays == NULL || max_relays < 1) { + return KNOT_EINVAL; + } + + uint32_t now = get_timestamp(); + memset(relays, 0, max_relays * sizeof(*relays)); + knot_tcp_relay_t *rl = relays, *rl_max = rl + max_relays; + + ssize_t free_conns = (ssize_t)(tcp_table->usage - limit_conn_count); + ssize_t free_inbuf = (ssize_t)(tcp_table->inbufs_total - MIN(limit_ibuf_size, SSIZE_MAX)); + ssize_t free_outbuf = (ssize_t)(tcp_table->outbufs_total - MIN(limit_obuf_size, SSIZE_MAX)); + + // reset connections to free ibufs + while (free_inbuf > 0 && rl != rl_max) { + if (tcp_table->next_ibuf->inbuf.iov_len == 0) { // this conn might have get rid of ibuf in the meantime + next_ptr_ibuf(&tcp_table->next_ibuf); + } + assert(tcp_table->next_ibuf != NULL); + rl->conn = tcp_table->next_ibuf; + sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf, + stats, KNOT_SWEEP_CTR_LIMIT_IBUF); + rl++; + } + + // reset connections to free obufs + while (free_outbuf > 0 && rl != rl_max) { + if (knot_tcp_outbufs_usage(tcp_table->next_obuf->outbufs) == 0) { + next_ptr_obuf(&tcp_table->next_obuf); + } + assert(tcp_table->next_obuf != NULL); + rl->conn = tcp_table->next_obuf; + sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf, + stats, KNOT_SWEEP_CTR_LIMIT_OBUF); + rl++; + } + + // reset connections to free their count, and old ones + knot_tcp_conn_t *conn, *next; + WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(tcp_table)) { + if ((free_conns <= 0 && now - conn->last_active < reset_timeout) || rl == rl_max) { + break; + } + + rl->conn = conn; + sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf, + stats, KNOT_SWEEP_CTR_LIMIT_CONN); + rl++; + } + + // close old connections + while (tcp_table->next_close != NULL && + now - tcp_table->next_close->last_active >= close_timeout && + rl != rl_max) { + if (tcp_table->next_close->state != XDP_TCP_CLOSING1) { + rl->conn = tcp_table->next_close; + rl->answer = XDP_TCP_CLOSE; + knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_TIMEOUT); + rl++; + } + next_node_ptr(&tcp_table->next_close); + } + + // resend unACKed data + while (tcp_table->next_resend != NULL && + now - tcp_table->next_resend->last_active >= resend_timeout && + rl != rl_max) { + rl->conn = tcp_table->next_resend; + rl->answer = XDP_TCP_RESEND; + rl++; + next_ptr_obuf(&tcp_table->next_resend); + } + + return KNOT_EOK; +} + +_public_ +void knot_tcp_cleanup(knot_tcp_table_t *tcp_table, knot_tcp_relay_t relays[], + uint32_t relay_count) +{ + (void)tcp_table; + for (uint32_t i = 0; i < relay_count; i++) { + if (relays[i].answer & XDP_TCP_FREE) { + del_conn(relays[i].conn); + } + free(relays[i].inbufs); + } +} diff --git a/src/libknot/xdp/tcp.h b/src/libknot/xdp/tcp.h new file mode 100644 index 0000000..da0f1a4 --- /dev/null +++ b/src/libknot/xdp/tcp.h @@ -0,0 +1,227 @@ +/* Copyright (C) 2022 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 TCP over XDP IO interface. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include "libknot/xdp/msg.h" +#include "libknot/xdp/xdp.h" + +struct knot_sweep_stats; + +typedef enum { + XDP_TCP_NOOP = 0, + XDP_TCP_SYN = 1, + XDP_TCP_ESTABLISH = 2, + XDP_TCP_CLOSE = 3, + XDP_TCP_RESET = 4, + XDP_TCP_RESEND = 5, + + XDP_TCP_FREE = 0x10, +} knot_tcp_action_t; + +typedef enum { + XDP_TCP_NORMAL, + XDP_TCP_ESTABLISHING, + XDP_TCP_CLOSING1, // FIN+ACK sent + XDP_TCP_CLOSING2, // FIN+ACK received and sent +} knot_tcp_state_t; + +typedef enum { + XDP_TCP_FREE_NONE, + XDP_TCP_FREE_DATA, + XDP_TCP_FREE_PREFIX, +} knot_tcp_relay_free_t; + +typedef enum { + XDP_TCP_IGNORE_NONE = 0, + XDP_TCP_IGNORE_ESTABLISH = (1 << 0), + XDP_TCP_IGNORE_DATA_ACK = (1 << 1), + XDP_TCP_IGNORE_FIN = (1 << 2), +} knot_tcp_ignore_t; + +typedef struct knot_tcp_conn { + struct { + struct knot_tcp_conn *list_node_next; + struct knot_tcp_conn *list_node_prev; + } list_node_placeholder; + struct sockaddr_in6 ip_rem; + struct sockaddr_in6 ip_loc; + uint8_t last_eth_rem[ETH_ALEN]; + uint8_t last_eth_loc[ETH_ALEN]; + uint16_t mss; + uint8_t window_scale; + uint32_t seqno; + uint32_t ackno; + uint32_t acked; + uint32_t window_size; + uint32_t last_active; + uint32_t establish_rtt; // in microseconds + knot_tcp_state_t state; + struct iovec inbuf; + struct knot_tcp_outbuf *outbufs; + struct knot_tcp_conn *next; +} knot_tcp_conn_t; + +typedef struct { + size_t size; + size_t usage; + size_t inbufs_total; + size_t outbufs_total; + uint64_t hash_secret[2]; + knot_tcp_conn_t *next_close; + knot_tcp_conn_t *next_ibuf; + knot_tcp_conn_t *next_obuf; + knot_tcp_conn_t *next_resend; + knot_tcp_conn_t *conns[]; +} knot_tcp_table_t; + +typedef struct { + const knot_xdp_msg_t *msg; + knot_tcp_action_t action; + knot_xdp_msg_flag_t auto_answer; + uint32_t auto_seqno; + knot_tcp_action_t answer; + struct iovec *inbufs; + size_t inbufs_count; + knot_tcp_conn_t *conn; +} knot_tcp_relay_t; + +/*! + * \brief Return next TCP sequence number. + */ +inline static uint32_t knot_tcp_next_seqno(const knot_xdp_msg_t *msg) +{ + uint32_t res = msg->seqno + msg->payload.iov_len; + if (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_FIN)) { + res++; + } + return res; +} + +/*! + * \brief Check if the relay is empty. + */ +inline static bool knot_tcp_relay_empty(const knot_tcp_relay_t *relay) +{ + return relay->action == XDP_TCP_NOOP && relay->answer == XDP_TCP_NOOP && + relay->auto_answer == 0 && relay->inbufs_count == 0; +} + +/*! + * \brief Allocate TCP connection-handling hash table. + * + * \param size Number of records for the hash table. + * \param secret_share Optional: share the hashing secret with another table. + * + * \note Hashing conflicts are solved by single-linked-lists in each record. + * + * \return The table, or NULL. + */ +knot_tcp_table_t *knot_tcp_table_new(size_t size, knot_tcp_table_t *secret_share); + +/*! + * \brief Free TCP connection hash table including all connection records. + * + * \note The freed connections are not closed nor reset. + */ +void knot_tcp_table_free(knot_tcp_table_t *table); + +/*! + * \brief Process received packets, prepare automatic responses (e.g. ACK), pick incoming data. + * + * \param relays Out: relays to be filled with message/connection details. + * \param msgs Packets received by knot_xdp_recv(). + * \param msg_count Number of received packets. + * \param tcp_table Table of TCP connections. + * \param syn_table Optional: extra table for handling partially established connections. + * \param ignore Ignore specific TCP packets indication. + * + * \return KNOT_E* + */ +int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count, + knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, + knot_tcp_ignore_t ignore); + +/*! + * \brief Prepare data (payload) to be sent as a response on specific relay. + * + * \param relay Relay with active connection. + * \param tcp_table TCP table. + * \param ignore_lastbyte Evil mode: drop last byte of the payload. + * \param data Data payload, possibly > MSS and > window. + * \param len Payload length, < 64k. + * + * \return KNOT_E* + */ +int knot_tcp_reply_data(knot_tcp_relay_t *relay, knot_tcp_table_t *tcp_table, + bool ignore_lastbyte, uint8_t *data, uint32_t len); + +/*! + * \brief Send TCP packets. + * + * \param socket XDP socket to send through. + * \param relays Connection changes and data. + * \param relay_count Number of connection changes and data. + * \param max_at_once Limit of packet batch sent by knot_xdp_send(). + * + * \return KNOT_E* + */ +int knot_tcp_send(knot_xdp_socket_t *socket, knot_tcp_relay_t relays[], + uint32_t relay_count, uint32_t max_at_once); + +/*! + * \brief Cleanup old TCP connections, perform timeout checks. + * + * \param tcp_table TCP connection table to clean up. + * \param close_timeout Gracefully close connections older than this (usecs). + * \param reset_timeout Reset connections older than this (usecs). + * \param resend_timeout Resend unAcked data older than this (usecs). + * \param limit_conn_count Limit of active connections in TCP table, reset if more. + * \param limit_ibuf_size Limit of memory usage by input buffers, reset if exceeded. + * \param limit_obuf_size Limit of memory usage by output buffers, reset if exceeded. + * \param relays Out: relays to be filled with close/reset instructions for knot_tcp_send(). + * \param max_relays Maximum relays to be used. + * \param stats Out: sweeped out connection statistics. + * + * \return KNOT_E* + */ +int knot_tcp_sweep(knot_tcp_table_t *tcp_table, + uint32_t close_timeout, uint32_t reset_timeout, + uint32_t resend_timeout, uint32_t limit_conn_count, + size_t limit_ibuf_size, size_t limit_obuf_size, + knot_tcp_relay_t *relays, uint32_t max_relays, + struct knot_sweep_stats *stats); + +/*! + * \brief Free resources of closed/reset connections. + * + * \param tcp_table TCP table with connections. + * \param relays Relays with closed/reset (or other, ignored) connections. + * \param relay_count Number of relays. + */ +void knot_tcp_cleanup(knot_tcp_table_t *tcp_table, knot_tcp_relay_t relays[], + uint32_t relay_count); + +/*! @} */ diff --git a/src/libknot/xdp/tcp_iobuf.c b/src/libknot/xdp/tcp_iobuf.c new file mode 100644 index 0000000..88ebcc3 --- /dev/null +++ b/src/libknot/xdp/tcp_iobuf.c @@ -0,0 +1,266 @@ +/* Copyright (C) 2022 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/xdp/tcp_iobuf.h" + +#include "contrib/macros.h" +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/error.h" + +static void iov_clear(struct iovec *iov) +{ + free(iov->iov_base); + memset(iov, 0, sizeof(*iov)); +} + +static void iov_inc(struct iovec *iov, size_t shift) +{ + assert(shift <= iov->iov_len); + iov->iov_base += shift; + iov->iov_len -= shift; +} + +/*! \brief Strip 2-byte length prefix from a payload. */ +static void iov_inc2(struct iovec *iov) +{ + iov_inc(iov, sizeof(uint16_t)); +} + +static size_t tcp_payload_len(const struct iovec *payload) +{ + assert(payload->iov_len >= 2); + uint16_t val; + memcpy(&val, payload->iov_base, sizeof(val)); + return be16toh(val) + sizeof(val); +} + +static bool iov_inc_pf(struct iovec *iov) +{ + size_t shift = tcp_payload_len(iov); + if (iov->iov_len >= shift) { + iov_inc(iov, shift); + return true; + } else { + return false; + } +} + +static size_t iov_count(const struct iovec *iov) +{ + size_t res = 0; + struct iovec tmp = *iov; + while (tmp.iov_len >= sizeof(uint16_t) && iov_inc_pf(&tmp)) { + res++; + } + return res; +} + +static void iov_append(struct iovec *what, const struct iovec *with) +{ + // NOTE: what->iov_base must be pre-allocated large enough + memcpy(what->iov_base + what->iov_len, with->iov_base, with->iov_len); + what->iov_len += with->iov_len; +} + +_public_ +int knot_tcp_inbuf_update(struct iovec *buffer, struct iovec data, + struct iovec **inbufs, size_t *inbufs_count, + size_t *buffers_total) +{ + size_t res_count = 0; + struct iovec *res = NULL, *cur = NULL; + + *inbufs = NULL; + *inbufs_count = 0; + + if (data.iov_len < 1) { + return KNOT_EOK; + } + if (buffer->iov_len == 1) { + ((uint8_t *)buffer->iov_base)[1] = ((uint8_t *)data.iov_base)[0]; + buffer->iov_len++; + iov_inc(&data, 1); + if (data.iov_len < 1) { + return KNOT_EOK; + } + } + if (buffer->iov_len > 0) { + size_t buffer_req = tcp_payload_len(buffer); + assert(buffer_req > buffer->iov_len); + struct iovec data_use = { data.iov_base, buffer_req - buffer->iov_len }; + if (data_use.iov_len <= data.iov_len) { // usable payload combined from buffer and data ---> res[0] allocated tohether with res + iov_inc(&data, data_use.iov_len); + + res_count = 1 + iov_count(&data); + res = malloc(res_count * sizeof(*res) + buffer_req); + if (res == NULL) { + return KNOT_ENOMEM; + } + res[0].iov_base = (void *)(res + res_count); + res[0].iov_len = 0; + iov_append(&res[0], buffer); + iov_append(&res[0], &data_use); + assert(res[0].iov_len == buffer_req); + iov_inc2(&res[0]); + + cur = &res[1]; + *buffers_total -= buffer->iov_len; + iov_clear(buffer); + } else { // just extend the buffer with data + void *bufnew = realloc(buffer->iov_base, buffer->iov_len + data.iov_len); + if (bufnew == NULL) { + return KNOT_ENOMEM; + } + buffer->iov_base = bufnew; + iov_append(buffer, &data); + *buffers_total += data.iov_len; + return KNOT_EOK; + } + } else { // just allocate res + res_count = iov_count(&data); + if (res_count > 0) { + res = malloc(res_count * sizeof(*res)); + if (res == NULL) { + return KNOT_ENOMEM; + } + cur = &res[0]; + } + } + + void *last; + while (data.iov_len > 1) { + last = data.iov_base; + if (!iov_inc_pf(&data)) { + break; + } + assert(cur); + cur->iov_base = last; + cur->iov_len = data.iov_base - last; + iov_inc2(cur); + cur++; + } + assert(cur == ((res_count) ? res + res_count : res)); + + // store the final incomplete payload to buffer + if (data.iov_len > 0) { + assert(buffer->iov_base == NULL); + buffer->iov_base = malloc(MAX(data.iov_len, 2)); + if (buffer->iov_base == NULL) { + free(res); + return KNOT_ENOMEM; + } + *buffers_total += MAX(data.iov_len, 2); + buffer->iov_len = 0; + iov_append(buffer, &data); + } + + *inbufs = res; + *inbufs_count = res_count; + + return KNOT_EOK; +} + +_public_ +int knot_tcp_outbufs_add(knot_tcp_outbuf_t **bufs, uint8_t *data, size_t len, + bool ignore_lastbyte, uint32_t mss, size_t *outbufs_total) +{ + if (len > UINT16_MAX) { + return KNOT_ELIMIT; + } + knot_tcp_outbuf_t **end = bufs; + while (*end != NULL) { // NOTE: this can be optimized by adding "end" pointer for the price of larger knot_tcp_conn_t struct + end = &(*end)->next; + } + uint16_t prefix = htobe16(len), prefix_len = sizeof(prefix); + while (len > 0) { + uint16_t newlen = MIN(len + prefix_len, mss); + knot_tcp_outbuf_t *newob = calloc(1, sizeof(*newob) + newlen); + if (newob == NULL) { + return KNOT_ENOMEM; + } + *outbufs_total += sizeof(*newob) + newlen; + newob->len = newlen; + if (ignore_lastbyte) { + newob->len--; + } + memcpy(newob->bytes, &prefix, prefix_len); + memcpy(newob->bytes + prefix_len, data, newlen - prefix_len); + + *end = newob; + end = &newob->next; + + data += newlen - prefix_len; + len -= newlen - prefix_len; + + prefix_len = 0; + } + return KNOT_EOK; +} + +static bool seqno_lower(uint32_t seqno, uint32_t ackno, uint32_t ackno_min) +{ + if (ackno_min <= ackno) { + return (seqno >= ackno_min && seqno <= ackno); + } else { + return (seqno >= ackno_min || seqno <= ackno); + } +} + +_public_ +void knot_tcp_outbufs_ack(knot_tcp_outbuf_t **bufs, uint32_t ackno, size_t *outbufs_total) +{ + uint32_t ackno_min = ackno - (UINT32_MAX / 2); // FIXME better? + while (*bufs != NULL && (*bufs)->sent && seqno_lower((*bufs)->seqno + (*bufs)->len, ackno, ackno_min)) { + knot_tcp_outbuf_t *tofree = *bufs; + *bufs = tofree->next; + *outbufs_total -= tofree->len + sizeof(*tofree); + free(tofree); + } +} + +_public_ +void knot_tcp_outbufs_can_send(knot_tcp_outbuf_t *bufs, ssize_t window_size, bool resend, + knot_tcp_outbuf_t **send_start, size_t *send_count) +{ + *send_count = 0; + *send_start = bufs; + while (*send_start != NULL && (*send_start)->sent && !resend) { + window_size -= (*send_start)->len; + *send_start = (*send_start)->next; + } + + knot_tcp_outbuf_t *can_send = *send_start; + while (can_send != NULL && window_size >= can_send->len) { + (*send_count)++; + window_size -= can_send->len; + can_send = can_send->next; + } +} + +_public_ +size_t knot_tcp_outbufs_usage(knot_tcp_outbuf_t *bufs) +{ + size_t res = 0; + for (knot_tcp_outbuf_t *i = bufs; i != NULL; i = i->next) { + res += i->len + sizeof(*i); + } + return res; +} diff --git a/src/libknot/xdp/tcp_iobuf.h b/src/libknot/xdp/tcp_iobuf.h new file mode 100644 index 0000000..a27eeab --- /dev/null +++ b/src/libknot/xdp/tcp_iobuf.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2022 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 TCP buffer helpers. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/uio.h> + +typedef struct knot_tcp_outbuf { + struct knot_tcp_outbuf *next; + uint32_t len; + uint32_t seqno; + bool sent; + uint8_t bytes[]; +} knot_tcp_outbuf_t; + +typedef enum { + KNOT_SWEEP_CTR_TIMEOUT = 0, + KNOT_SWEEP_CTR_LIMIT_CONN = 1, + KNOT_SWEEP_CTR_LIMIT_IBUF = 2, + KNOT_SWEEP_CTR_LIMIT_OBUF = 3, +} knot_sweep_counter_t; + +typedef struct knot_sweep_stats { + uint32_t total; + uint32_t counters[4]; +} knot_sweep_stats_t; + +inline static void knot_sweep_stats_incr(knot_sweep_stats_t *stats, knot_sweep_counter_t counter) +{ + (stats->counters[counter])++; + (stats->total)++; +} + +inline static void knot_sweep_stats_reset(knot_sweep_stats_t *stats) +{ + memset(stats, 0, sizeof(*stats)); +} + +/*! + * \brief Handle DNS-over-TCP payloads in buffer and message. + * + * \param buffer In/out: persistent buffer to store incomplete DNS payloads between receiving packets. + * \param data In: momental DNS payloads in incoming packet. + * \param inbufs Out: list of incoming DNS messages. + * \param inbufs_count Out: number of inbufs. + * \param buffers_total In/Out: total size of buffers (will be increased or decreased). + * + * \return KNOT_EOK, KNOT_ENOMEM + */ +int knot_tcp_inbuf_update(struct iovec *buffer, struct iovec data, + struct iovec **inbufs, size_t *inbufs_count, + size_t *buffers_total); + +/*! + * \brief Add payload to be sent by TCP, to output buffers. + * + * \param bufs Output buffers to be updated. + * \param data Payload to be sent. + * \param len Payload length. + * \param ignore_lastbyte Evil mode: drop last byte of the payload. + * \param mss Connection outgoing MSS. + * \param outbufs_total In/out: total outbuf statistic to be updated. + * + * \return KNOT_E* + */ +int knot_tcp_outbufs_add(knot_tcp_outbuf_t **bufs, uint8_t *data, size_t len, + bool ignore_lastbyte, uint32_t mss, size_t *outbufs_total); + +/*! + * \brief Remove+free acked data from output buffers. + * + * \param bufs Output buffers to be updated. + * \param ackno Ackno of received ACK. + * \param outbufs_total In/out: total outbuf statistic to be updated. + */ +void knot_tcp_outbufs_ack(knot_tcp_outbuf_t **bufs, uint32_t ackno, size_t *outbufs_total); + +/*! + * \brief Prepare output buffers to be sent now. + * + * \param bufs Output buffers to be updated. + * \param window_size Connection outgoing window size. + * \param resend Send also possibly already sent data. + * \param send_start Out: first output buffer to be sent. + * \param send_count Out: number of output buffers to be sent. + */ +void knot_tcp_outbufs_can_send(knot_tcp_outbuf_t *bufs, ssize_t window_size, bool resend, + knot_tcp_outbuf_t **send_start, size_t *send_count); + +/*! + * \brief Compute allocated size of output buffers. + */ +size_t knot_tcp_outbufs_usage(knot_tcp_outbuf_t *bufs); + +/*! @} */ diff --git a/src/libknot/xdp/xdp.c b/src/libknot/xdp/xdp.c new file mode 100644 index 0000000..975276d --- /dev/null +++ b/src/libknot/xdp/xdp.c @@ -0,0 +1,568 @@ +/* Copyright (C) 2022 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 <netinet/in.h> +#include <linux/if_ether.h> +#include <linux/udp.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/bpf-user.h" +#include "libknot/xdp/eth.h" +#include "libknot/xdp/msg_init.h" +#include "libknot/xdp/protocols.h" +#include "libknot/xdp/xdp.h" +#include "contrib/macros.h" +#include "contrib/net.h" + +#define FRAME_SIZE 2048 + +#define FRAME_COUNT_TX 2048 +#define FRAME_COUNT_RX 2048 +#define FRAME_COUNT (FRAME_COUNT_TX + FRAME_COUNT_RX) + +#define RING_LEN_TX FRAME_COUNT_TX +#define RING_LEN_CQ FRAME_COUNT_TX +#define RING_LEN_RX FRAME_COUNT_RX +/* It's recommended that the FQ ring size >= HW RX ring size + AF_XDP RX ring size. */ +#define RING_LEN_FQ 8192 + +#define ALLOC_RETRY_NUM 15 +#define ALLOC_RETRY_DELAY 20 // In nanoseconds. + +/* 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(RING_LEN_TX) && IS_POWER_OF_2(RING_LEN_RX) + && IS_POWER_OF_2(RING_LEN_CQ) && IS_POWER_OF_2(RING_LEN_FQ) + && FRAME_COUNT_TX <= (1 << 16) /* see tx_free_indices */ + , "Incorrect #define combination for AF_XDP."); +#endif + +struct umem_frame { + uint8_t bytes[FRAME_SIZE]; +}; + +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]) * FRAME_COUNT_TX); + if (umem == NULL) { + return KNOT_ENOMEM; + } + + int ret = posix_memalign((void **)&umem->frames, getpagesize(), + FRAME_SIZE * FRAME_COUNT); + if (ret != 0) { + free(umem); + return KNOT_ENOMEM; + } + + const struct xsk_umem_config config = { + .fill_size = RING_LEN_FQ, + .comp_size = RING_LEN_CQ, + .frame_size = FRAME_SIZE, + .frame_headroom = KNOT_XDP_PKT_ALIGNMENT, + }; + + ret = xsk_umem__create(&umem->umem, umem->frames, FRAME_SIZE * 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 = FRAME_COUNT_TX; + for (uint32_t i = 0; i < 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, FRAME_COUNT_RX, &idx); + if (ret != FRAME_COUNT_RX) { + assert(0); + return KNOT_ERROR; + } + assert(idx == 0); + assert(FRAME_COUNT == FRAME_COUNT_TX + FRAME_COUNT_RX); + for (uint32_t i = FRAME_COUNT_TX; i < FRAME_COUNT; ++i) { + *xsk_ring_prod__fill_addr(&umem->fq, idx++) = i * FRAME_SIZE; + } + xsk_ring_prod__submit(&umem->fq, 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 = RING_LEN_TX, + .rx_size = 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, + knot_xdp_filter_flag_t flags, uint16_t udp_port, uint16_t quic_port, + knot_xdp_load_bpf_t load_bpf, const void *xdp_config) +{ + if (socket == NULL || if_name == NULL || + (udp_port == quic_port && (flags & KNOT_XDP_FILTER_UDP) && (flags & KNOT_XDP_FILTER_QUIC)) || + (flags & (KNOT_XDP_FILTER_UDP | KNOT_XDP_FILTER_TCP | KNOT_XDP_FILTER_QUIC)) == 0) { + 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; + } + + (*socket)->frame_limit = FRAME_SIZE; + ret = knot_eth_mtu(if_name); + if (ret > 0) { + (*socket)->frame_limit = MIN((unsigned)ret, (*socket)->frame_limit); + } + + if (flags & KNOT_XDP_FILTER_ROUTE) { + ret = knot_eth_vlans(&(*socket)->vlan_map, &(*socket)->vlan_map_max); + if (ret != KNOT_EOK) { + xsk_socket__delete((*socket)->xsk); + deconfigure_xsk_umem(umem); + kxsk_iface_free(iface); + free(*socket); + *socket = NULL; + return ret; + } + } + + ret = kxsk_socket_start(iface, flags, udp_port, quic_port, (*socket)->xsk); + if (ret != KNOT_EOK) { + free((*socket)->vlan_map); + 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; + } + if (unlikely(socket->send_mock != NULL)) { + free(socket); + 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->vlan_map); + 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 < 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 || unlikely(socket->send_mock != 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 <= 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(knot_xdp_socket_t *socket) +{ + if (unlikely(socket->send_mock != NULL)) { + return malloc(sizeof(struct umem_frame)); + } + + const struct timespec delay = { .tv_nsec = ALLOC_RETRY_DELAY }; + struct kxsk_umem *umem = socket->umem; + + for (int i = 0; unlikely(umem->tx_free_count == 0); i++) { + if (i == ALLOC_RETRY_NUM) { + return NULL; + } + nanosleep(&delay, NULL); + knot_xdp_send_prepare(socket); + } + + uint32_t index = umem->tx_free_indices[--umem->tx_free_count]; + return umem->frames + index; +} + +static void prepare_payload(knot_xdp_msg_t *msg, void *uframe) +{ + size_t hdr_len = prot_write_hdrs_len(msg); + msg->payload.iov_base = uframe + hdr_len + KNOT_XDP_PKT_ALIGNMENT; + msg->payload.iov_len = FRAME_SIZE - hdr_len - KNOT_XDP_PKT_ALIGNMENT; +} + +_public_ +int knot_xdp_send_alloc(knot_xdp_socket_t *socket, knot_xdp_msg_flag_t flags, + knot_xdp_msg_t *out) +{ + if (socket == NULL || out == NULL) { + return KNOT_EINVAL; + } + + struct umem_frame *uframe = alloc_tx_frame(socket); + if (uframe == NULL) { + return KNOT_ENOMEM; + } + + msg_init(out, flags); + prepare_payload(out, uframe); + + return KNOT_EOK; +} + +_public_ +int knot_xdp_reply_alloc(knot_xdp_socket_t *socket, const knot_xdp_msg_t *query, + knot_xdp_msg_t *out) +{ + if (socket == NULL || query == NULL || out == NULL) { + return KNOT_EINVAL; + } + + struct umem_frame *uframe = alloc_tx_frame(socket); + if (uframe == NULL) { + return KNOT_ENOMEM; + } + + msg_init_reply(out, query); + prepare_payload(out, uframe); + + return KNOT_EOK; +} + +static void free_unsent(knot_xdp_socket_t *socket, const knot_xdp_msg_t *msg) +{ + if (unlikely(socket->send_mock != NULL)) { + free(msg->payload.iov_base - prot_write_hdrs_len(msg) - KNOT_XDP_PKT_ALIGNMENT); + return; + } + uint64_t addr_relative = (uint8_t *)msg->payload.iov_base + - socket->umem->frames->bytes; + tx_free_relative(socket->umem, addr_relative); +} + +_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; + } + if (unlikely(socket->send_mock != NULL)) { + int ret = socket->send_mock(socket, msgs, count, sent); + for (uint32_t i = 0; i < count; ++i) { + free_unsent(socket, &msgs[i]); + } + return ret; + } + + /* 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. + */ + if (xsk_prod_nb_free(&socket->tx, count) < count) { + /* This situation was sometimes observed in the emulated XDP mode. */ + for (uint32_t i = 0; i < count; ++i) { + free_unsent(socket, &msgs[i]); + } + return KNOT_ENOBUFS; + } + uint32_t idx = socket->tx.cached_prod; + + for (uint32_t i = 0; i < count; ++i) { + const knot_xdp_msg_t *msg = &msgs[i]; + + if (empty_msg(msg)) { + free_unsent(socket, msg); + } else { + size_t hdr_len = prot_write_hdrs_len(msg); + size_t tot_len = hdr_len + msg->payload.iov_len; + uint8_t *msg_beg = msg->payload.iov_base - hdr_len; + uint16_t mss = MIN(socket->frame_limit - hdr_len, KNOT_TCP_MSS); + prot_write_eth(msg_beg, msg, msg_beg + tot_len, mss); + + *xsk_ring_prod__tx_desc(&socket->tx, idx++) = (struct xdp_desc) { + .addr = msg_beg - socket->umem->frames->bytes, + .len = tot_len, + }; + } + } + + *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_ +void knot_xdp_send_free(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count) +{ + for (uint32_t i = 0; i < count; i++) { + free_unsent(socket, &msgs[i]); + } +} + +_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. + */ +} + +_public_ +int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[], + uint32_t max_count, uint32_t *count, size_t *wire_size) +{ + 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) { + knot_xdp_msg_t *msg = &msgs[i]; + const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&socket->rx, idx++); + uint8_t *uframe_p = (uint8_t *)socket->umem->frames + desc->addr; + + void *payl_end; + void *payl_start = prot_read_eth(uframe_p, msg, &payl_end, + socket->vlan_map, socket->vlan_map_max); + + msg->payload.iov_base = payl_start; + msg->payload.iov_len = payl_end - payl_start; + msg->mss = MIN(msg->mss, FRAME_SIZE - (payl_start - (void *)uframe_p)); + + if (wire_size != NULL) { + (*wire_size) += desc->len; + } + } + + xsk_ring_cons__release(&socket->rx, available); + *count = available; + + return KNOT_EOK; +} + +static uint8_t *msg_uframe_ptr(const knot_xdp_msg_t *msg) +{ + return NULL + ((msg->payload.iov_base - NULL) & ~(FRAME_SIZE - 1)); +} + +_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(&msgs[i]); + 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)(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)(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..a7606fe --- /dev/null +++ b/src/libknot/xdp/xdp.h @@ -0,0 +1,189 @@ +/* Copyright (C) 2022 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 XDP IO interface. + * + * \addtogroup xdp + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <netinet/in.h> + +#include "libknot/xdp/bpf-consts.h" +#include "libknot/xdp/msg.h" + +/*! + * \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 Initialize XDP socket. + * + * \param socket XDP socket. + * \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 flags XDP filter configuration flags. + * \param udp_port UDP and/or TCP port to listen on if enabled via \a opts. + * \param quic_port QUIC/UDP port to listen on if enabled via \a opts. + * \param load_bpf Insert BPF program into packet processing. + * \param xdp_config Optional XDP socket configuration. + * + * \return KNOT_E* or -errno + */ +int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue, + knot_xdp_filter_flag_t flags, uint16_t udp_port, uint16_t quic_port, + knot_xdp_load_bpf_t load_bpf, const void *xdp_config); + +/*! + * \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 incoming 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 flags Flags for new message. + * \param out Out: the allocated packet buffer. + * + * \return KNOT_E* + */ +int knot_xdp_send_alloc(knot_xdp_socket_t *socket, knot_xdp_msg_flag_t flags, + knot_xdp_msg_t *out); + +/*! + * \brief Allocate one buffer for a reply packet. + * + * \param socket XDP socket. + * \param query The packet to be replied to. + * \param out Out: the allocated packet buffer. + * + * \return KNOT_E* + */ +int knot_xdp_reply_alloc(knot_xdp_socket_t *socket, const knot_xdp_msg_t *query, + knot_xdp_msg_t *out); + +/*! + * \brief Send multiple packets thru XDP. + * + * \note The packets all must have been allocated by knot_xdp_send_alloc()! + * \note Do not free the packet 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 Cleanup messages that have not been knot_xdp_send(). + * + * ...possibly due to some error. + * + * \param socket XDP socket. + * \param msgs Messages to be freed. + * \param count Number of messages. + */ +void knot_xdp_send_free(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], + uint32_t count); + +/*! + * \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 incoming packets. + * \param max_count Limit for number of packets received at once. + * \param count Out: real number of received packets. + * \param wire_size Out: (optional) total wire size 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, size_t *wire_size); + +/*! + * \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..3004761 --- /dev/null +++ b/src/libknot/yparser/yparser.c @@ -0,0 +1,176 @@ +/* Copyright (C) 2021 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; + } + + // Check for empty file (cannot mmap). + if (file_stat.st_size > 0) { + // Map the file to the memory. + char *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 */ + + // Set the parser input limits. + parser->input.start = start; + parser->input.current = start; + parser->input.end = start + file_stat.st_size; + + parser->input.eof = false; + } else { + parser->input.eof = true; + } + + parser->file.name = strdup(file_name); + + 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..88e0e00 --- /dev/null +++ b/src/libknot/yparser/yparser.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2022 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 127 +/*! Maximal length of textual data value. */ +#define YP_MAX_TXT_DATA_LEN 32767 + +/*! 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 + 1]; + /*! [out] Key length. */ + size_t key_len; + /*! [out] Parsed data (zero terminated string). */ + char data[YP_MAX_TXT_DATA_LEN + 1]; + /*! [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..ad9cf1f --- /dev/null +++ b/src/libknot/yparser/ypbody.c @@ -0,0 +1,460 @@ + +/* Copyright (C) 2021 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, 14, 16, 27, 29, 32, 43, + 44, 54, 65, 67, 68, 78, 90, 94, + 96, 99, 104, 107, 110, 122, 132, 142, + 145, 148, 151, 156, 159, 162, 176, 190, + 204 +}; + +static const char _yparser_trans_keys[] = { + 10, 13, 32, 35, 42, 45, 92, 95, + 46, 57, 65, 90, 97, 122, 10, 13, + 32, 42, 58, 92, 95, 45, 57, 65, + 90, 97, 122, 32, 58, 10, 13, 32, + 32, 42, 58, 92, 95, 45, 57, 65, + 90, 97, 122, 32, 32, 42, 92, 95, + 46, 57, 65, 90, 97, 122, 32, 42, + 58, 92, 95, 45, 57, 65, 90, 97, + 122, 32, 58, 32, 32, 34, 35, 44, + 92, 127, 0, 31, 91, 93, 10, 13, + 32, 44, 92, 127, 0, 31, 34, 35, + 91, 93, 10, 13, 32, 35, 10, 13, + 34, 32, 126, 34, 92, 127, 0, 31, + 10, 13, 32, 34, 32, 126, 10, 13, + 32, 34, 35, 44, 91, 92, 93, 127, + 0, 31, 32, 34, 35, 44, 92, 127, + 0, 31, 91, 93, 32, 44, 91, 92, + 93, 127, 0, 31, 34, 35, 32, 44, + 93, 10, 13, 32, 34, 32, 126, 34, + 92, 127, 0, 31, 32, 44, 93, 34, + 32, 126, 10, 13, 32, 35, 42, 45, + 92, 95, 46, 57, 65, 90, 97, 122, + 10, 13, 32, 35, 42, 45, 92, 95, + 46, 57, 65, 90, 97, 122, 10, 13, + 32, 35, 42, 45, 92, 95, 46, 57, + 65, 90, 97, 122, 10, 13, 32, 35, + 42, 45, 92, 95, 46, 57, 65, 90, + 97, 122, 0 +}; + +static const char _yparser_single_lengths[] = { + 0, 8, 2, 5, 2, 3, 5, 1, + 4, 5, 2, 1, 6, 6, 4, 2, + 1, 3, 3, 1, 10, 6, 6, 3, + 3, 1, 3, 3, 1, 8, 8, 8, + 8 +}; + +static const char _yparser_range_lengths[] = { + 0, 3, 0, 3, 0, 0, 3, 0, + 3, 3, 0, 0, 2, 3, 0, 0, + 1, 1, 0, 1, 1, 2, 2, 0, + 0, 1, 1, 0, 1, 3, 3, 3, + 3 +}; + +static const unsigned char _yparser_index_offsets[] = { + 0, 0, 12, 15, 24, 27, 31, 40, + 42, 50, 59, 62, 64, 73, 83, 88, + 91, 94, 99, 103, 106, 118, 127, 136, + 140, 144, 147, 152, 156, 159, 171, 183, + 195 +}; + +static const char _yparser_indicies[] = { + 1, 2, 3, 4, 5, 6, 5, 5, + 5, 5, 5, 0, 1, 2, 4, 7, + 8, 9, 8, 8, 8, 8, 8, 0, + 10, 11, 0, 12, 13, 14, 0, 15, + 16, 17, 16, 16, 16, 16, 16, 0, + 18, 0, 18, 19, 19, 19, 19, 19, + 19, 0, 20, 21, 22, 21, 21, 21, + 21, 21, 0, 23, 24, 0, 25, 0, + 25, 27, 0, 0, 28, 0, 0, 0, + 26, 30, 31, 32, 0, 33, 0, 0, + 0, 0, 29, 12, 13, 34, 35, 0, + 12, 13, 35, 36, 29, 0, 38, 39, + 0, 0, 37, 30, 31, 32, 0, 40, + 37, 0, 12, 13, 14, 27, 35, 0, + 41, 28, 0, 0, 0, 26, 41, 43, + 0, 0, 44, 0, 0, 0, 42, 46, + 47, 0, 48, 49, 0, 0, 0, 45, + 50, 41, 51, 0, 12, 13, 34, 0, + 52, 45, 0, 54, 55, 0, 0, 53, + 46, 47, 49, 0, 56, 53, 0, 1, + 2, 3, 4, 57, 6, 57, 57, 57, + 57, 57, 0, 59, 60, 61, 62, 63, + 64, 63, 63, 63, 63, 63, 58, 65, + 66, 67, 68, 69, 70, 69, 69, 69, + 69, 69, 0, 71, 72, 73, 74, 75, + 76, 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, 13, 32, 29, + 14, 16, 14, 15, 13, 17, 18, 19, + 17, 21, 22, 26, 25, 22, 23, 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, 9, 43, 11, + 11, 9, 0, 0, 40, 9, 0, 9, + 40, 0, 37, 7, 37, 9, 11, 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..0ac5b57 --- /dev/null +++ b/src/libknot/yparser/ypschema.c @@ -0,0 +1,598 @@ +/* Copyright (C) 2021 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 referenced section. + 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; + + // Get referenced group if supported. + const yp_name_t *grp_ref_name = dst->var.r.grp_ref_name; + if (grp_ref_name != NULL) { + const yp_item_t *grp_ref = yp_schema_find(grp_ref_name, NULL, schema); + if (grp_ref == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + dst->var.r.grp_ref = grp_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..91e5165 --- /dev/null +++ b/src/libknot/yparser/ypschema.h @@ -0,0 +1,352 @@ +/* Copyright (C) 2021 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; + /*! Boolean 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 transformation function. */ + int (*to_txt)(YP_BIN_TXT_PARAMS); + } d; + /*! Reference variables. */ + struct { + /*! Referenced section name. */ + yp_name_t const *ref_name; + /*! Referenced group section name (optional). */ + yp_name_t const *grp_ref_name; + /*! Referenced section item (dynamic value). */ + yp_item_t const *ref; + /*! Referenced group section item (dynamic value). */ + yp_item_t const *grp_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..7b040d9 --- /dev/null +++ b/src/libknot/yparser/yptrafo.c @@ -0,0 +1,1099 @@ +/* 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/musl/inet_ntop.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 (knot_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 (knot_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 +); + +/*! @} */ |