summaryrefslogtreecommitdiffstats
path: root/src/libknot/probe
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot/probe')
-rw-r--r--src/libknot/probe/data.c135
-rw-r--r--src/libknot/probe/data.h132
-rw-r--r--src/libknot/probe/probe.c228
-rw-r--r--src/libknot/probe/probe.h116
4 files changed, 611 insertions, 0 deletions
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);
+
+/*! @} */