summaryrefslogtreecommitdiffstats
path: root/src/libknot/probe/probe.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot/probe/probe.c')
-rw-r--r--src/libknot/probe/probe.c228
1 files changed, 228 insertions, 0 deletions
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
+}