summaryrefslogtreecommitdiffstats
path: root/include/dnsjit/output
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-17 07:11:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-17 07:11:16 +0000
commita68848db159cc1cafa82f9d383432fda459c8745 (patch)
tree5fd1cd2cd6f298bebbbb0ce5db29fa6de68a2acc /include/dnsjit/output
parentAdding debian version 1.1.0+debian-1. (diff)
downloaddnsjit-a68848db159cc1cafa82f9d383432fda459c8745.tar.xz
dnsjit-a68848db159cc1cafa82f9d383432fda459c8745.zip
Merging upstream version 1.2.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'include/dnsjit/output')
-rw-r--r--include/dnsjit/output/dnscli.c888
-rw-r--r--include/dnsjit/output/dnscli.h38
-rw-r--r--include/dnsjit/output/dnscli.hh72
-rw-r--r--include/dnsjit/output/dnscli.lua187
-rw-r--r--include/dnsjit/output/null.lua31
-rw-r--r--include/dnsjit/output/pcap.c120
-rw-r--r--include/dnsjit/output/pcap.h32
-rw-r--r--include/dnsjit/output/pcap.hh42
-rw-r--r--include/dnsjit/output/pcap.lua93
-rw-r--r--include/dnsjit/output/respdiff.c298
-rw-r--r--include/dnsjit/output/respdiff.h31
-rw-r--r--include/dnsjit/output/respdiff.hh36
-rw-r--r--include/dnsjit/output/respdiff.lua94
-rw-r--r--include/dnsjit/output/tcpcli.c381
-rw-r--r--include/dnsjit/output/tcpcli.h32
-rw-r--r--include/dnsjit/output/tcpcli.hh51
-rw-r--r--include/dnsjit/output/tcpcli.lua131
-rw-r--r--include/dnsjit/output/tlscli.c345
-rw-r--r--include/dnsjit/output/tlscli.h34
-rw-r--r--include/dnsjit/output/tlscli.hh52
-rw-r--r--include/dnsjit/output/tlscli.lua103
-rw-r--r--include/dnsjit/output/udpcli.c300
-rw-r--r--include/dnsjit/output/udpcli.h35
-rw-r--r--include/dnsjit/output/udpcli.hh53
-rw-r--r--include/dnsjit/output/udpcli.lua121
25 files changed, 3600 insertions, 0 deletions
diff --git a/include/dnsjit/output/dnscli.c b/include/dnsjit/output/dnscli.c
new file mode 100644
index 0000000..f7c5b5e
--- /dev/null
+++ b/include/dnsjit/output/dnscli.c
@@ -0,0 +1,888 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnscli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+#include "core/object/udp.h"
+#include "core/object/tcp.h"
+
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+
+static inline uint16_t _need16(const void* ptr)
+{
+ uint16_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be16toh(v);
+}
+
+static core_log_t _log = LOG_T_INIT("output.dnscli");
+static output_dnscli_t _defaults = {
+ LOG_T_INIT_OBJ("output.dnscli"),
+ OUTPUT_DNSCLI_MODE_NONE,
+ 0, 0, 0, -1, 0, 0,
+ { 0, 0, 0 }, 0,
+ { 0 }, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0), 0, 0, 0, 0, 0,
+ { 0, 0 },
+ 0, 0
+};
+
+core_log_t* output_dnscli_log()
+{
+ return &_log;
+}
+
+void output_dnscli_init(output_dnscli_t* self, output_dnscli_mode_t mode)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->mode = mode;
+ self->pkt.payload = self->recvbuf;
+
+ switch (mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS: {
+ int err;
+ if ((err = gnutls_certificate_allocate_credentials(&self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_certificate_allocate_credentials() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_init(&self->session, GNUTLS_CLIENT | ((mode & OUTPUT_DNSCLI_MODE_NONBLOCKING) ? GNUTLS_NONBLOCK : 0))) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_init() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_set_default_priority(self->session)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_set_default_priority() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE, self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_credentials_set() error: %s", gnutls_strerror(err));
+ }
+
+ gnutls_handshake_set_timeout(self->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ break;
+ }
+ default:
+ lfatal("Invalid mode %x", mode);
+ }
+}
+
+void output_dnscli_destroy(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ if (self->session) {
+ gnutls_bye(self->session, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(self->session);
+ }
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ if (self->cred) {
+ gnutls_certificate_free_credentials(self->cred);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int output_dnscli_connect(output_dnscli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ memcpy(&self->addr, addr->ai_addr, addr->ai_addrlen);
+ self->addr_len = addr->ai_addrlen;
+ freeaddrinfo(addr);
+
+ if ((self->fd = socket(((struct sockaddr*)&self->addr)->sa_family, SOCK_DGRAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ return -2;
+ }
+ break;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ case OUTPUT_DNSCLI_MODE_TLS:
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+ break;
+ default:
+ break;
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ if (self->mode & OUTPUT_DNSCLI_MODE_NONBLOCKING) {
+ int flags;
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -3;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -3;
+ }
+ self->nonblocking = 1;
+ }
+ if (self->timeout.sec > 0 || self->timeout.nsec > 0) {
+ self->poll.fd = self->fd;
+ self->poll_timeout = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!self->poll_timeout) {
+ self->poll_timeout = 1;
+ }
+ }
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS: {
+ unsigned int ms;
+ gnutls_transport_set_int(self->session, self->fd);
+ ms = (self->timeout.sec * 1000) + (self->timeout.nsec / 1000000);
+ if (!ms && self->timeout.nsec) {
+ ms = 1;
+ }
+ gnutls_record_set_timeout(self->session, ms);
+
+ /* Establish TLS */
+ do {
+ err = gnutls_handshake(self->session);
+ } while (err < 0 && gnutls_error_is_fatal(err) == 0);
+ if (err == GNUTLS_E_PREMATURE_TERMINATION) {
+ lcritical("gnutls_handshake() error: %s", gnutls_strerror(err));
+ return -3;
+ } else if (err < 0) {
+ lcritical("gnutls_handshake() failed: %s (%d)\n", gnutls_strerror(err), err);
+ return -3;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ self->conn_ok = 1;
+ return 0;
+}
+
+inline ssize_t _send_udp(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ if (self->poll_timeout) {
+ self->poll.events = POLLOUT;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLOUT)) {
+ if (!n) {
+ self->timeouts++;
+ return -1;
+ }
+ self->errs++;
+ return -2;
+ }
+ }
+ n = sendto(self->fd, payload + sent, len - sent, 0, (struct sockaddr*)&self->addr, self->addr_len);
+ if (n > -1) {
+ return n;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_udp(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+
+ if (((core_object_dns_t*)obj)->includes_dnslen) {
+ if (len < 2) {
+ return;
+ }
+ payload += 2;
+ len -= 2;
+ }
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_udp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+inline ssize_t _send_tcp(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ if (self->poll_timeout) {
+ self->poll.events = POLLOUT;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLOUT)) {
+ if (!n) {
+ self->timeouts++;
+ return -1;
+ }
+ self->errs++;
+ return -2;
+ }
+ }
+ n = sendto(self->fd, payload + sent, len - sent, 0, 0, 0);
+ if (n > -1) {
+ return n;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_tcp(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ uint16_t dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ for (;;) {
+ n = _send_tcp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ break;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ self->errs++;
+ return;
+ }
+ sent = 0;
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_tcp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+inline ssize_t _send_tls(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ n = gnutls_record_send(self->session, payload + sent, len - sent);
+ if (n > -1) {
+ return n;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ case GNUTLS_E_INTERRUPTED:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_tls(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ uint16_t dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ for (;;) {
+ n = _send_tls(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ break;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ self->errs++;
+ return;
+ }
+ sent = 0;
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_tls(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+luajit_ssize_t output_dnscli_send(output_dnscli_t* self, const core_object_t* obj, size_t sent)
+{
+ const uint8_t* payload;
+ size_t len;
+ uint16_t dnslen;
+ mlassert_self();
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+
+ if (((core_object_dns_t*)obj)->includes_dnslen) {
+ if (len < 2) {
+ return -2;
+ }
+ payload += 2;
+ len -= 2;
+ }
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_udp(self, payload, len, sent);
+
+ case OUTPUT_DNSCLI_MODE_TCP:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ if (sent < sizeof(dnslen)) {
+ dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ return _send_tcp(self, payload, len, sent);
+ }
+ sent -= sizeof(dnslen);
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_tcp(self, payload, len, sent);
+
+ case OUTPUT_DNSCLI_MODE_TLS:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ if (sent < sizeof(dnslen)) {
+ dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ return _send_tls(self, payload, len, sent);
+ }
+ sent -= sizeof(dnslen);
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_tls(self, payload, len, sent);
+
+ default:
+ break;
+ }
+
+ return -2;
+}
+
+core_receiver_t output_dnscli_receiver(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (!self->conn_ok) {
+ lfatal("not connected");
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ return (core_receiver_t)_receive_udp;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ return (core_receiver_t)_receive_tcp;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ return (core_receiver_t)_receive_tls;
+ default:
+ break;
+ }
+
+ lfatal("internal error");
+ return 0;
+}
+
+static const core_object_t* _produce_udp(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ for (;;) {
+ if (self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_tcp(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ if (self->have_pkt) {
+ if (self->recv > self->dnslen + sizeof(self->dnslen)) {
+ self->recv -= self->dnslen + sizeof(self->dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(self->dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+ self->have_pkt = 0;
+ self->have_dnslen = 0;
+ }
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ if (self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = recvfrom(self->fd, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv, 0, 0, 0);
+ if (n > 0) {
+ self->recv += n;
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ if (self->nonblocking) {
+ break;
+ }
+ continue;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_tls(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ if (self->have_pkt) {
+ if (self->recv > self->dnslen + sizeof(self->dnslen)) {
+ self->recv -= self->dnslen + sizeof(self->dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(self->dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+ self->have_pkt = 0;
+ self->have_dnslen = 0;
+ }
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ if (!gnutls_record_check_pending(self->session) && self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = gnutls_record_recv(self->session, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv);
+ if (n > 0) {
+ self->recv += n;
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ if (self->nonblocking) {
+ break;
+ }
+ continue;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ case GNUTLS_E_INTERRUPTED:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_dnscli_producer(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (!self->conn_ok) {
+ lfatal("not connected");
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ return (core_producer_t)_produce_udp;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ return (core_producer_t)_produce_tcp;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ return (core_producer_t)_produce_tls;
+ default:
+ break;
+ }
+
+ lfatal("internal error");
+ return 0;
+}
diff --git a/include/dnsjit/output/dnscli.h b/include/dnsjit/output/dnscli.h
new file mode 100644
index 0000000..9927bd4
--- /dev/null
+++ b/include/dnsjit/output/dnscli.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+#include <dnsjit/core/producer.h>
+#include <dnsjit/core/object/payload.h>
+#include <dnsjit/core/timespec.h>
+#include <dnsjit/core/compat.h>
+
+#ifndef __dnsjit_output_dnscli_h
+#define __dnsjit_output_dnscli_h
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <gnutls/gnutls.h>
+#include <poll.h>
+
+#include <dnsjit/output/dnscli.hh>
+
+#endif
diff --git a/include/dnsjit/output/dnscli.hh b/include/dnsjit/output/dnscli.hh
new file mode 100644
index 0000000..4126423
--- /dev/null
+++ b/include/dnsjit/output/dnscli.hh
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef enum output_dnscli_mode {
+ OUTPUT_DNSCLI_MODE_NONE = 0,
+ OUTPUT_DNSCLI_MODE_OPTIONS = 0xf,
+ OUTPUT_DNSCLI_MODE_NONBLOCKING = 0x1,
+ OUTPUT_DNSCLI_MODE_MODES = 0xf0,
+ OUTPUT_DNSCLI_MODE_UDP = 0x10,
+ OUTPUT_DNSCLI_MODE_TCP = 0x20,
+ OUTPUT_DNSCLI_MODE_TLS = 0x30,
+} output_dnscli_mode_t;
+
+typedef struct output_dnscli {
+ core_log_t _log;
+
+ output_dnscli_mode_t mode;
+
+ size_t pkts, errs, timeouts;
+ int fd, nonblocking, conn_ok;
+
+ struct pollfd poll;
+ int poll_timeout;
+
+ struct sockaddr_storage addr;
+ size_t addr_len;
+
+ uint8_t recvbuf[(64 * 1024) + 2];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen, have_pkt;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+} output_dnscli_t;
+
+core_log_t* output_dnscli_log();
+
+void output_dnscli_init(output_dnscli_t* self, output_dnscli_mode_t mode);
+void output_dnscli_destroy(output_dnscli_t* self);
+int output_dnscli_connect(output_dnscli_t* self, const char* host, const char* port);
+luajit_ssize_t output_dnscli_send(output_dnscli_t* self, const core_object_t* obj, size_t sent);
+
+core_receiver_t output_dnscli_receiver(output_dnscli_t* self);
+core_producer_t output_dnscli_producer(output_dnscli_t* self);
diff --git a/include/dnsjit/output/dnscli.lua b/include/dnsjit/output/dnscli.lua
new file mode 100644
index 0000000..bfdaf36
--- /dev/null
+++ b/include/dnsjit/output/dnscli.lua
@@ -0,0 +1,187 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.dnscli
+-- DNS aware UDP/TCP/TLS client
+-- local dnscli = require("dnsjit.output.dnscli")
+-- .SS UDP Receiver Chain
+-- local output = dnscli.new(dnscli.UDP)
+-- output:connect("127.0.0.1", "53")
+-- input:receiver(output)
+-- .SS TCP Nonblocking
+-- local output = dnscli.new(dnscli.TCP + dnscli.NONBLOCKING)
+-- output:send(object)
+--
+-- The DNS client can a
+-- .I core.object.dns
+-- or a
+-- .I core.object.payload
+-- object via the receiver interface or using
+-- .I send()
+-- and send it as DNS query after which it can receive the response by using
+-- the producer interface.
+-- If the object being sent is a
+-- .I core.object.dns
+-- then it will look at
+-- .I includes_dnslen
+-- attribute and depending on the protocol it will disregard, include or send
+-- the DNS length as an extra packet.
+-- If the object being sent is a
+-- .I core.object.payload
+-- then no special handling will be done and it will be sent as is.
+-- When receiving responses the producer interface will generate
+-- .I core.object.payload
+-- objects which may include the DNS length depending on the protocol used and
+-- must be handled by the caller.
+-- .SS MODES
+-- These transport modes and options are available when creating a new Dnscli
+-- output.
+-- .TP
+-- UDP
+-- Create an output using UDP.
+-- .TP
+-- TCP
+-- Create an output using TCP.
+-- .TP
+-- TLS
+-- Create an output using TCP and encrypt it with TLS.
+-- .TP
+-- NONBLOCKING
+-- Make the client nonblocking, see
+-- .I send()
+-- and
+-- .IR produce() .
+module(...,package.seeall)
+
+require("dnsjit.output.dnscli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_dnscli_t"
+local output_dnscli_t = ffi.typeof(t_name)
+local Dnscli = {
+ NONBLOCKING = 0x1,
+ UDP = 0x10,
+ TCP = 0x20,
+ TLS = 0x30,
+}
+
+-- Create a new Dnscli output.
+function Dnscli.new(mode)
+ local self = {
+ obj = output_dnscli_t(),
+ }
+ C.output_dnscli_init(self.obj, mode)
+ ffi.gc(self.obj, C.output_dnscli_destroy)
+ return setmetatable(self, { __index = Dnscli })
+end
+
+-- Set or return the timeout used for sending and reciving, must be used before
+-- .IR connect() .
+function Dnscli:timeout(seconds, nanoseconds)
+ if seconds == nil and nanoseconds == nil then
+ return self.obj.timeout
+ end
+ if nanoseconds == nil then
+ nanoseconds = 0
+ end
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Dnscli:connect(host, port)
+ return C.output_dnscli_connect(self.obj, host, port)
+end
+
+-- Return if nonblocking mode is on (true) or off (false).
+function Dnscli:nonblocking()
+ if self.obj.nonblocking == 1 then
+ return true
+ end
+ return false
+end
+
+-- Send an object and optionally continue sending after
+-- .I sent
+-- bytes.
+-- Unlike the receive interface this function lets you know if the sending was
+-- successful or not which might be needed on nonblocking connections.
+-- Returns -2 on error, -1 if interrupted, timed out or unable to send due to
+-- nonblocking, or the number of bytes sent.
+-- .B Note
+-- the counters for sent, received, errors and timeouts are not affected by
+-- this function.
+function Dnscli:send(object, sent)
+ if sent == nil then
+ sent = 0
+ end
+ return C.output_dnscli_send(self.obj, object, sent)
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Dnscli:receive()
+ return C.output_dnscli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- The producer returns nil on error.
+function Dnscli:produce()
+ return C.output_dnscli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Dnscli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of successful
+-- calls to
+-- .IR recvfrom (2)
+-- that returned data.
+function Dnscli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending or receiving.
+function Dnscli:errors()
+ return tonumber(self.obj.errs)
+end
+
+-- Return the number of timeouts when sending or receiving.
+function Dnscli:timeouts()
+ return tonumber(self.obj.timeouts)
+end
+
+-- core.object.dns (3),
+-- core.object.payload (3),
+-- core.timespec (3)
+return Dnscli
diff --git a/include/dnsjit/output/null.lua b/include/dnsjit/output/null.lua
new file mode 100644
index 0000000..b0e167f
--- /dev/null
+++ b/include/dnsjit/output/null.lua
@@ -0,0 +1,31 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.null
+-- Dummy layer to example.output.null
+--
+-- This module has moved to example.output.null, see examples/modules/output-example in
+-- dnsjit source repository.
+module(...,package.seeall)
+
+ok, cls = pcall(require, "example.output.null")
+if not ok then
+ error("You need to install the example module output-example\n" .. cls)
+end
+
+return cls
diff --git a/include/dnsjit/output/pcap.c b/include/dnsjit/output/pcap.c
new file mode 100644
index 0000000..ede9881
--- /dev/null
+++ b/include/dnsjit/output/pcap.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/pcap.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+static core_log_t _log = LOG_T_INIT("output.pcap");
+static output_pcap_t _defaults = {
+ LOG_T_INIT_OBJ("output.pcap"),
+ 0, 0
+};
+
+core_log_t* output_pcap_log()
+{
+ return &_log;
+}
+
+void output_pcap_init(output_pcap_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void output_pcap_destroy(output_pcap_t* self)
+{
+ mlassert_self();
+}
+
+int output_pcap_open(output_pcap_t* self, const char* file, int linktype, int snaplen)
+{
+ mlassert_self();
+ if (self->dumper) {
+ lfatal("PCAP already opened");
+ }
+
+ if (!(self->pcap = pcap_open_dead(linktype, snaplen))) {
+ lcritical("pcap_open_dead() failed");
+ return -1;
+ }
+
+ if (!(self->dumper = pcap_dump_open(self->pcap, file))) {
+ lcritical("pcap_dump_open() error: %s", pcap_geterr(self->pcap));
+ pcap_close(self->pcap);
+ self->pcap = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+void output_pcap_close(output_pcap_t* self)
+{
+ mlassert_self();
+ if (self->dumper) {
+ pcap_dump_close(self->dumper);
+ self->dumper = 0;
+ }
+ if (self->pcap) {
+ pcap_close(self->pcap);
+ self->pcap = 0;
+ }
+}
+
+int output_pcap_have_errors(output_pcap_t* self)
+{
+ mlassert_self();
+ if (self->dumper) {
+ return ferror(pcap_dump_file(self->dumper));
+ }
+ return 0;
+}
+
+static void _receive(output_pcap_t* self, const core_object_t* obj)
+{
+ struct pcap_pkthdr hdr;
+ mlassert_self();
+
+ while (obj) {
+ if (obj->obj_type == CORE_OBJECT_PCAP) {
+ hdr.ts.tv_sec = ((const core_object_pcap_t*)obj)->ts.sec;
+ hdr.ts.tv_usec = ((const core_object_pcap_t*)obj)->ts.nsec / 1000;
+ hdr.caplen = ((const core_object_pcap_t*)obj)->caplen;
+ hdr.len = ((const core_object_pcap_t*)obj)->len;
+
+ pcap_dump((void*)self->dumper, &hdr, ((const core_object_pcap_t*)obj)->bytes);
+ return;
+ }
+ obj = obj->obj_prev;
+ }
+}
+
+core_receiver_t output_pcap_receiver(output_pcap_t* self)
+{
+ if (!self->dumper) {
+ lfatal("PCAP not opened");
+ }
+
+ return (core_receiver_t)_receive;
+}
diff --git a/include/dnsjit/output/pcap.h b/include/dnsjit/output/pcap.h
new file mode 100644
index 0000000..40146c2
--- /dev/null
+++ b/include/dnsjit/output/pcap.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+#include <dnsjit/core/producer.h>
+
+#ifndef __dnsjit_output_pcap_h
+#define __dnsjit_output_pcap_h
+
+#include <pcap/pcap.h>
+
+#include <dnsjit/output/pcap.hh>
+
+#endif
diff --git a/include/dnsjit/output/pcap.hh b/include/dnsjit/output/pcap.hh
new file mode 100644
index 0000000..6205333
--- /dev/null
+++ b/include/dnsjit/output/pcap.hh
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if 0
+typedef struct pcap_dumper {} pcap_dumper_t;
+#endif
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.input.pcap_h")
+
+typedef struct output_pcap {
+ core_log_t _log;
+ pcap_t* pcap;
+ pcap_dumper_t* dumper;
+} output_pcap_t;
+
+core_log_t* output_pcap_log();
+void output_pcap_init(output_pcap_t* self);
+void output_pcap_destroy(output_pcap_t* self);
+int output_pcap_open(output_pcap_t* self, const char* file, int linktype, int snaplen);
+void output_pcap_close(output_pcap_t* self);
+int output_pcap_have_errors(output_pcap_t* self);
+
+core_receiver_t output_pcap_receiver(output_pcap_t* self);
diff --git a/include/dnsjit/output/pcap.lua b/include/dnsjit/output/pcap.lua
new file mode 100644
index 0000000..26ae8e7
--- /dev/null
+++ b/include/dnsjit/output/pcap.lua
@@ -0,0 +1,93 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.pcap
+-- Output to a PCAP using libpcap
+-- local output = require("dnsjit.output.pcap").new()
+-- output:open("file.pcap")
+-- ...
+-- output:close()
+--
+-- Output module for writing
+-- .I dnsjit.core.object.pcap
+-- objects to a PCAP,
+module(...,package.seeall)
+
+require("dnsjit.output.pcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_pcap_t"
+local output_pcap_t = ffi.typeof(t_name)
+local Pcap = {}
+
+-- Create a new Pcap output.
+function Pcap.new()
+ local self = {
+ obj = output_pcap_t(),
+ }
+ C.output_pcap_init(self.obj)
+ ffi.gc(self.obj, C.output_pcap_destroy)
+ return setmetatable(self, { __index = Pcap })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Pcap:log()
+ if self == nil then
+ return C.output_pcap_log()
+ end
+ return self.obj._log
+end
+
+-- Open the PCAP
+-- .I file
+-- to write to using the
+-- .I linktype
+-- and
+-- .IR snaplen .
+-- Uses
+-- .B pcap_dump_open()
+-- so you can pass "-" to it to open stdout, see it's man-page for more
+-- information.
+-- Returns 0 on success.
+function Pcap:open(file, linktype, snaplen)
+ return C.output_pcap_open(self.obj, file, linktype, snaplen)
+end
+
+-- Close the PCAP.
+function Pcap:close()
+ C.output_pcap_close(self.obj)
+end
+
+-- Return true if the underlying
+-- .I FILE*
+-- indicates that there's been an error.
+function Pcap:have_errors()
+ if C.output_pcap_have_errors(self.obj) == 0 then
+ return false
+ end
+ return true
+end
+
+-- Return the C functions and context for receiving objects.
+function Pcap:receive()
+ return C.output_pcap_receiver(self.obj), self.obj
+end
+
+-- dnsjit.input.pcap (3)
+return Pcap
diff --git a/include/dnsjit/output/respdiff.c b/include/dnsjit/output/respdiff.c
new file mode 100644
index 0000000..834a264
--- /dev/null
+++ b/include/dnsjit/output/respdiff.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/respdiff.h"
+#include "core/assert.h"
+#include "core/object/payload.h"
+
+#ifdef HAVE_LMDB_H
+#include <lmdb.h>
+#endif
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+
+static core_log_t _log = LOG_T_INIT("output.respdiff");
+static output_respdiff_t _defaults = {
+ LOG_T_INIT_OBJ("output.respdiff"),
+ 0, 0, 0, 0, 0, 0
+};
+
+core_log_t* output_respdiff_log()
+{
+ return &_log;
+}
+
+void output_respdiff_init(output_respdiff_t* self, const char* path, size_t mapsize)
+{
+ mlassert_self();
+
+ if (!path) {
+ lfatal("path is nil");
+ }
+
+ *self = _defaults;
+
+#ifdef HAVE_LMDB_H
+ if (mkdir(path, 0775) && errno != EEXIST) {
+ lfatal("mkdir(%s) error %s", path, core_log_errstr(errno));
+ }
+ if (mdb_env_create((MDB_env**)&self->env)) {
+ lfatal("mdb_env_create failed");
+ }
+ if (mdb_env_set_mapsize((MDB_env*)self->env, mapsize)) {
+ lfatal("mdb_env_set_mapsize(%lu) failed", mapsize);
+ }
+ if (mdb_env_set_maxdbs((MDB_env*)self->env, 3)) {
+ lfatal("mdb_env_set_maxdbs failed");
+ }
+ if (mdb_env_open((MDB_env*)self->env, path, 0, 0664)) {
+ lfatal("mdb_env_open(%s) failed", path);
+ }
+ if (mdb_txn_begin((MDB_env*)self->env, 0, 0, (MDB_txn**)&self->txn)) {
+ lfatal("mdb_txn_begin failed for queries");
+ }
+ lfatal_oom(self->qdb = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "queries", MDB_CREATE, (MDB_dbi*)self->qdb)) {
+ lfatal("mdb_dbi_open failed for queries");
+ }
+ lfatal_oom(self->rdb = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "answers", MDB_CREATE, (MDB_dbi*)self->rdb)) {
+ lfatal("mdb_dbi_open failed for responses");
+ }
+ lfatal_oom(self->meta = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "meta", MDB_CREATE, (MDB_dbi*)self->meta)) {
+ lfatal("mdb_dbi_open failed for meta");
+ }
+#endif
+}
+
+void output_respdiff_destroy(output_respdiff_t* self)
+{
+ mlassert_self();
+
+#ifdef HAVE_LMDB_H
+ if (self->env) {
+ mdb_env_close((MDB_env*)self->env);
+ }
+ free(self->qdb);
+ free(self->rdb);
+ free(self->meta);
+#endif
+}
+
+#ifdef HAVE_LMDB_H
+static const char* _meta_version = "version";
+static const char* _meta_version_val = "2018-05-21";
+static const char* _meta_servers = "servers";
+static const char* _meta_name0 = "name0";
+static const char* _meta_name1 = "name1";
+static const char* _meta_start_time = "start_time";
+static const char* _meta_end_time = "end_time";
+#endif
+
+void output_respdiff_commit(output_respdiff_t* self, const char* origname, const char* recvname, uint64_t start_time, uint64_t end_time)
+{
+#ifdef HAVE_LMDB_H
+ MDB_val k, v;
+ uint32_t i;
+ int err;
+ mlassert_self();
+ lassert(origname, "origname is nil");
+ lassert(recvname, "recvname is nil");
+
+ k.mv_size = strlen(_meta_version);
+ k.mv_data = (void*)_meta_version;
+ v.mv_size = strlen(_meta_version_val);
+ v.mv_data = (void*)_meta_version_val;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.version failed, database is full");
+ } else {
+ lfatal("mdb_put meta.version failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_servers);
+ k.mv_data = (void*)_meta_servers;
+ i = 2;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.servers failed, database is full");
+ } else {
+ lfatal("mdb_put meta.servers failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_name0);
+ k.mv_data = (void*)_meta_name0;
+ v.mv_size = strlen(origname);
+ v.mv_data = (void*)origname;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.name0 failed, database is full");
+ } else {
+ lfatal("mdb_put meta.name0 failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_name1);
+ k.mv_data = (void*)_meta_name1;
+ v.mv_size = strlen(recvname);
+ v.mv_data = (void*)recvname;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.name1 failed, database is full");
+ } else {
+ lfatal("mdb_put meta.name1 failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_start_time);
+ k.mv_data = (void*)_meta_start_time;
+ i = start_time;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.start_time failed, database is full");
+ } else {
+ lfatal("mdb_put meta.start_time failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_end_time);
+ k.mv_data = (void*)_meta_end_time;
+ i = end_time;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.end_time failed, database is full");
+ } else {
+ lfatal("mdb_put meta.end_time failed (%d)", err);
+ }
+ }
+
+ if (self->txn) {
+ if (mdb_txn_commit((MDB_txn*)self->txn)) {
+ lfatal("mdb_txn_commit failed");
+ }
+ self->txn = 0;
+ }
+#endif
+}
+
+#ifdef HAVE_LMDB_H
+static void _receive(output_respdiff_t* self, const core_object_t* obj)
+{
+ const core_object_payload_t *query, *original, *response;
+ MDB_val k, v;
+ uint8_t responses[132096];
+ uint32_t msec;
+ uint16_t dnslen;
+ int err;
+ mlassert_self();
+
+ if (!obj || obj->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid first object");
+ }
+ query = (core_object_payload_t*)obj;
+
+ if (!query->obj_prev || query->obj_prev->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid second object");
+ }
+ original = (core_object_payload_t*)query->obj_prev;
+
+ response = (core_object_payload_t*)original->obj_prev;
+ if (response && response->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid third object");
+ }
+
+ if (12 + original->len + (response ? response->len : 0) > sizeof(responses)) {
+ lfatal("not enough buffer space for responses");
+ }
+
+ self->count++;
+
+ k.mv_size = sizeof(self->id);
+ k.mv_data = (void*)&self->id;
+ v.mv_size = query->len;
+ v.mv_data = (void*)query->payload;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->qdb), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put query failed, database is full");
+ } else {
+ lfatal("mdb_put query failed (%d)", err);
+ }
+ }
+
+ msec = 1; // TODO
+ memcpy(responses, &msec, 4);
+ dnslen = original->len;
+ memcpy(&responses[4], &dnslen, 2);
+ memcpy(&responses[6], original->payload, original->len);
+ if (response) {
+ memcpy(&responses[6 + original->len], &msec, 4);
+ dnslen = response->len;
+ memcpy(&responses[10 + original->len], &dnslen, 2);
+ memcpy(&responses[12 + original->len], response->payload, response->len);
+ } else {
+ msec = 0xffffffff;
+ memcpy(&responses[6 + original->len], &msec, 4);
+ dnslen = 0;
+ memcpy(&responses[10 + original->len], &dnslen, 2);
+ }
+
+ v.mv_size = 12 + original->len + (response ? response->len : 0);
+ v.mv_data = (void*)responses;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->rdb), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put answers failed, database is full");
+ } else {
+ lfatal("mdb_put answers failed (%d)", err);
+ }
+ }
+
+ self->id++;
+}
+
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self)
+{
+ mlassert_self();
+
+ if (!self->txn) {
+ lfatal("no LMDB opened");
+ }
+
+ return (core_receiver_t)_receive;
+}
+#else
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self)
+{
+ mlassert_self();
+ lfatal("no LMDB support");
+ return 0;
+}
+#endif
diff --git a/include/dnsjit/output/respdiff.h b/include/dnsjit/output/respdiff.h
new file mode 100644
index 0000000..207e225
--- /dev/null
+++ b/include/dnsjit/output/respdiff.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+
+#ifndef __dnsjit_output_respdiff_h
+#define __dnsjit_output_respdiff_h
+
+#include <stdint.h>
+
+#include <dnsjit/output/respdiff.hh>
+
+#endif
diff --git a/include/dnsjit/output/respdiff.hh b/include/dnsjit/output/respdiff.hh
new file mode 100644
index 0000000..44f597e
--- /dev/null
+++ b/include/dnsjit/output/respdiff.hh
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef struct output_respdiff {
+ core_log_t _log;
+ void * env, *txn, *qdb, *rdb, *meta;
+ uint32_t id;
+ size_t count;
+} output_respdiff_t;
+
+core_log_t* output_respdiff_log();
+void output_respdiff_init(output_respdiff_t* self, const char* path, size_t mapsize);
+void output_respdiff_destroy(output_respdiff_t* self);
+void output_respdiff_commit(output_respdiff_t* self, const char* origname, const char* recvname, uint64_t start_time, uint64_t end_time);
+
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self);
diff --git a/include/dnsjit/output/respdiff.lua b/include/dnsjit/output/respdiff.lua
new file mode 100644
index 0000000..7d17b4a
--- /dev/null
+++ b/include/dnsjit/output/respdiff.lua
@@ -0,0 +1,94 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.respdiff
+-- Output to respdiff LMDB
+-- local output = require("dnsjit.output.respdiff").new("/path/to/lmdb")
+--
+-- Output to an LMDB database (format 2018-05-21) that can be used by respdiff
+-- to compare the responses found in the input data with the responses
+-- received.
+-- The receive function expects to get a chain of 2 or 3
+-- .IR core.object.payload .
+-- For a completed query; The top of the chain is the query, after it the
+-- original response and then the received response.
+-- For a timed out query; The top of the chain is the query, after it the
+-- original response.
+module(...,package.seeall)
+
+require("dnsjit.output.respdiff_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_respdiff_t"
+local output_respdiff_t = ffi.typeof(t_name)
+local Respdiff = {}
+
+-- Create a new Respdiff output and created the LMDB database in the directory
+-- .IR path .
+-- The
+-- .I origname
+-- and
+-- .I recvname
+-- are used to populate the meta table, these names should be the same as
+-- what is configured in
+-- .IR respdiff.cfg .
+-- Optional
+-- .I mapsize
+-- can be given to increase the database size beyond the default size of 10MB.
+function Respdiff.new(path, origname, recvname, mapsize)
+ if mapsize == nil then
+ mapsize = 10485760
+ end
+ local self = {
+ obj = output_respdiff_t(),
+ path = path,
+ origname = origname,
+ recvname = recvname,
+ }
+ C.output_respdiff_init(self.obj, path, mapsize)
+ ffi.gc(self.obj, C.output_respdiff_destroy)
+ return setmetatable(self, { __index = Respdiff })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Respdiff:log()
+ if self == nil then
+ return C.output_respdiff_log()
+ end
+ return self.obj._log
+end
+
+-- Return the C functions and context for receiving objects.
+function Respdiff:receive()
+ return C.output_respdiff_receiver(self.obj), self.obj
+end
+
+-- Commit the LMDB transactions, can not store any more objects after this
+-- call.
+-- The given
+-- .I start_time
+-- and
+-- .I end_time
+-- are used to fill the meta table.
+function Respdiff:commit(start_time, end_time)
+ C.output_respdiff_commit(self.obj, self.origname, self.recvname, start_time, end_time)
+end
+
+-- respdiff " https://gitlab.nic.cz/knot/respdiff"
+return Respdiff
diff --git a/include/dnsjit/output/tcpcli.c b/include/dnsjit/output/tcpcli.c
new file mode 100644
index 0000000..f2b218b
--- /dev/null
+++ b/include/dnsjit/output/tcpcli.c
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/tcpcli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.tcpcli");
+static output_tcpcli_t _defaults = {
+ LOG_T_INIT_OBJ("output.tcpcli"),
+ 0, 0, -1,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0),
+ 0, 0, 0, 0,
+ { 5, 0 }, 1
+};
+
+core_log_t* output_tcpcli_log()
+{
+ return &_log;
+}
+
+void output_tcpcli_init(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+}
+
+void output_tcpcli_destroy(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ }
+}
+
+int output_tcpcli_connect(output_tcpcli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+ return 0;
+}
+
+int output_tcpcli_nonblocking(output_tcpcli_t* self)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ flags = fcntl(self->fd, F_GETFL);
+ if (flags != -1) {
+ flags = flags & O_NONBLOCK ? 1 : 0;
+ }
+
+ return flags;
+}
+
+int output_tcpcli_set_nonblocking(output_tcpcli_t* self, int nonblocking)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -1;
+ }
+
+ if (nonblocking) {
+ flags |= O_NONBLOCK;
+ self->blocking = 0;
+ } else {
+ flags &= ~O_NONBLOCK;
+ self->blocking = 1;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void _receive(output_tcpcli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ uint16_t dnslen;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ dnslen = htons(len);
+
+ for (;;) {
+ ssize_t ret = sendto(self->fd, ((uint8_t*)&dnslen) + sent, sizeof(dnslen) - sent, 0, 0, 0);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < sizeof(dnslen))
+ continue;
+
+ sent = 0;
+ for (;;) {
+ ssize_t ret = sendto(self->fd, payload + sent, len - sent, 0, 0, 0);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_tcpcli_receiver(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_tcpcli_t* self)
+{
+ ssize_t n, recv = 0;
+ uint16_t dnslen;
+ struct pollfd p;
+ int to = 0;
+ mlassert_self();
+
+ // Check if last recvfrom() got more then we needed
+ if (!self->have_dnslen && self->recv > self->dnslen) {
+ recv = self->recv - self->dnslen;
+ if (recv < sizeof(dnslen)) {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, recv);
+ } else {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, sizeof(dnslen));
+
+ if (recv > sizeof(dnslen)) {
+ self->recv = recv - sizeof(dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+
+ if (self->recv > self->dnslen) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ }
+ }
+
+ if (self->blocking) {
+ p.fd = self->fd;
+ p.events = POLLIN;
+ p.revents = 0;
+ to = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!to) {
+ to = 1;
+ }
+ }
+
+ if (!self->have_dnslen) {
+ for (;;) {
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ if (recv) {
+ self->errs++;
+ return 0;
+ }
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ n = recvfrom(self->fd, ((uint8_t*)&dnslen) + recv, sizeof(dnslen) - recv, 0, 0, 0);
+ if (n > 0) {
+ recv += n;
+ if (recv < sizeof(dnslen))
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+ self->recv = 0;
+ }
+
+ for (;;) {
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ n = recvfrom(self->fd, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv, 0, 0, 0);
+ if (n > 0) {
+ self->recv += n;
+ if (self->recv < self->dnslen)
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_tcpcli_producer(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/include/dnsjit/output/tcpcli.h b/include/dnsjit/output/tcpcli.h
new file mode 100644
index 0000000..83b7ea4
--- /dev/null
+++ b/include/dnsjit/output/tcpcli.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+#include <dnsjit/core/producer.h>
+#include <dnsjit/core/object/payload.h>
+#include <dnsjit/core/timespec.h>
+
+#ifndef __dnsjit_output_tcpcli_h
+#define __dnsjit_output_tcpcli_h
+
+#include <dnsjit/output/tcpcli.hh>
+
+#endif
diff --git a/include/dnsjit/output/tcpcli.hh b/include/dnsjit/output/tcpcli.hh
new file mode 100644
index 0000000..277b0dd
--- /dev/null
+++ b/include/dnsjit/output/tcpcli.hh
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_tcpcli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd;
+
+ uint8_t recvbuf[64 * 1024];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+ int8_t blocking;
+} output_tcpcli_t;
+
+core_log_t* output_tcpcli_log();
+
+void output_tcpcli_init(output_tcpcli_t* self);
+void output_tcpcli_destroy(output_tcpcli_t* self);
+int output_tcpcli_connect(output_tcpcli_t* self, const char* host, const char* port);
+int output_tcpcli_nonblocking(output_tcpcli_t* self);
+int output_tcpcli_set_nonblocking(output_tcpcli_t* self, int nonblocking);
+
+core_receiver_t output_tcpcli_receiver(output_tcpcli_t* self);
+core_producer_t output_tcpcli_producer(output_tcpcli_t* self);
diff --git a/include/dnsjit/output/tcpcli.lua b/include/dnsjit/output/tcpcli.lua
new file mode 100644
index 0000000..d57de88
--- /dev/null
+++ b/include/dnsjit/output/tcpcli.lua
@@ -0,0 +1,131 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.tcpcli
+-- Simple, length aware, TCP client
+-- local output = require("dnsjit.output.tcpcli").new("127.0.0.1", "53")
+--
+-- Simple TCP client that takes any payload you give it, sends the length of
+-- the payload as an unsigned 16 bit integer and then sends the payload.
+-- When receiving it will first retrieve the length of the payload as an
+-- unsigned 16 bit integer and it will stall until it gets, even if
+-- nonblocking mode is used.
+-- Then it will retrieve at least that amount of bytes, if nonblocking mode
+-- is used here then it will return a payload object with length zero if
+-- there was nothing to receive or if the full payload have not been received
+-- yet.
+-- Additional calls will continue retrieving the payload.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.tcpcli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_tcpcli_t"
+local output_tcpcli_t = ffi.typeof(t_name)
+local Tcpcli = {}
+
+-- Create a new Tcpcli output.
+function Tcpcli.new()
+ local self = {
+ obj = output_tcpcli_t(),
+ }
+ C.output_tcpcli_init(self.obj)
+ ffi.gc(self.obj, C.output_tcpcli_destroy)
+ return setmetatable(self, { __index = Tcpcli })
+end
+
+-- Set the timeout when producing objects.
+function Tcpcli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Tcpcli:connect(host, port)
+ return C.output_tcpcli_connect(self.obj, host, port)
+end
+
+-- Enable (true) or disable (false) nonblocking mode and
+-- return 0 if successful, if
+-- .I bool
+-- is not specified then return if nonblocking mode is on (true) or off (false).
+function Tcpcli:nonblocking(bool)
+ if bool == nil then
+ if C.output_tcpcli_nonblocking(self.obj) == 1 then
+ return true
+ end
+ return false
+ elseif bool == true then
+ return C.output_tcpcli_set_nonblocking(self.obj, 1)
+ else
+ return C.output_tcpcli_set_nonblocking(self.obj, 0)
+ end
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Tcpcli:receive()
+ return C.output_tcpcli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive or if the full payload
+-- have not been received yet.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- If a timeout happens during during the first stage, getting the length, it
+-- will fail and return nil.
+-- Additional calls will continue retrieving the payload.
+-- The producer returns nil on error.
+function Tcpcli:produce()
+ return C.output_tcpcli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Tcpcli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of completely
+-- received DNS messages.
+function Tcpcli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending.
+function Tcpcli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Tcpcli
diff --git a/include/dnsjit/output/tlscli.c b/include/dnsjit/output/tlscli.c
new file mode 100644
index 0000000..8c5947a
--- /dev/null
+++ b/include/dnsjit/output/tlscli.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/tlscli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.tlscli");
+static output_tlscli_t _defaults = {
+ LOG_T_INIT_OBJ("output.tlscli"),
+ 0, 0, -1, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0),
+ 0, 0, 0, 0,
+ { 5, 0 },
+ 0, 0
+};
+
+core_log_t* output_tlscli_log()
+{
+ return &_log;
+}
+
+void output_tlscli_init(output_tlscli_t* self)
+{
+ int err;
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+
+ gnutls_global_init();
+ if ((err = gnutls_certificate_allocate_credentials(&self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_certificate_allocate_credentials() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_init(&self->session, GNUTLS_CLIENT)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_init() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_set_default_priority(self->session)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_set_default_priority() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE, self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_credentials_set() error: %s", gnutls_strerror(err));
+ }
+
+ gnutls_handshake_set_timeout(self->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+}
+
+void output_tlscli_destroy(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ if (self->session) {
+ gnutls_bye(self->session, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(self->session);
+ }
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ if (self->cred) {
+ gnutls_certificate_free_credentials(self->cred);
+ }
+ }
+}
+
+int output_tlscli_connect(output_tlscli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ unsigned int ms;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+ if (self->tls_ok) {
+ lfatal("TLS already established");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+
+ gnutls_transport_set_int(self->session, self->fd);
+ ms = (self->timeout.sec * 1000) + (self->timeout.nsec / 1000000);
+ if (!ms && self->timeout.nsec) {
+ ms = 1;
+ }
+ gnutls_record_set_timeout(self->session, ms);
+
+ /* Establish TLS */
+ do {
+ err = gnutls_handshake(self->session);
+ } while (err < 0 && gnutls_error_is_fatal(err) == 0);
+ if (err == GNUTLS_E_PREMATURE_TERMINATION) {
+ lcritical("gnutls_handshake() error: %s", gnutls_strerror(err));
+ return -3;
+ } else if (err < 0) {
+ lcritical("gnutls_handshake() failed: %s (%d)\n", gnutls_strerror(err), err);
+ return -3;
+ }
+
+ self->tls_ok = 1;
+ return 0;
+}
+
+static void _receive(output_tlscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ uint16_t dnslen;
+ ssize_t ret;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ dnslen = htons(len);
+
+ for (;;) {
+ ret = gnutls_record_send(self->session, ((uint8_t*)&dnslen) + sent, sizeof(dnslen) - sent);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < sizeof(dnslen))
+ continue;
+
+ sent = 0;
+ for (;;) {
+ ret = gnutls_record_send(self->session, payload + sent, len - sent);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ return;
+ }
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_tlscli_receiver(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+ if (!self->tls_ok) {
+ lfatal("TLS is not established");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_tlscli_t* self)
+{
+ ssize_t n, recv = 0;
+ uint16_t dnslen;
+ mlassert_self();
+
+ // Check if last recvfrom() got more then we needed
+ if (!self->have_dnslen && self->recv > self->dnslen) {
+ recv = self->recv - self->dnslen;
+ if (recv < sizeof(dnslen)) {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, recv);
+ } else {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, sizeof(dnslen));
+
+ if (recv > sizeof(dnslen)) {
+ self->recv = recv - sizeof(dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+
+ if (self->recv > self->dnslen) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ }
+ }
+
+ if (!self->have_dnslen) {
+ for (;;) {
+ n = gnutls_record_recv(self->session, ((uint8_t*)&dnslen) + recv, sizeof(dnslen) - recv);
+ if (n > 0) {
+ recv += n;
+ if (recv < sizeof(dnslen))
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+ self->recv = 0;
+ }
+
+ for (;;) {
+ n = gnutls_record_recv(self->session, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv);
+ if (n > 0) {
+ self->recv += n;
+ if (self->recv < self->dnslen)
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_tlscli_producer(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+ if (!self->tls_ok) {
+ lfatal("TLS is not established");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/include/dnsjit/output/tlscli.h b/include/dnsjit/output/tlscli.h
new file mode 100644
index 0000000..72e7b9d
--- /dev/null
+++ b/include/dnsjit/output/tlscli.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+#include <dnsjit/core/producer.h>
+#include <dnsjit/core/object/payload.h>
+#include <dnsjit/core/timespec.h>
+
+#ifndef __dnsjit_output_tlscli_h
+#define __dnsjit_output_tlscli_h
+
+#include <gnutls/gnutls.h>
+
+#include <dnsjit/output/tlscli.hh>
+
+#endif
diff --git a/include/dnsjit/output/tlscli.hh b/include/dnsjit/output/tlscli.hh
new file mode 100644
index 0000000..4a0c142
--- /dev/null
+++ b/include/dnsjit/output/tlscli.hh
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_tlscli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd, tls_ok;
+
+ uint8_t recvbuf[64 * 1024];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+} output_tlscli_t;
+
+core_log_t* output_tlscli_log();
+
+void output_tlscli_init(output_tlscli_t* self);
+void output_tlscli_destroy(output_tlscli_t* self);
+int output_tlscli_connect(output_tlscli_t* self, const char* host, const char* port);
+
+core_receiver_t output_tlscli_receiver(output_tlscli_t* self);
+core_producer_t output_tlscli_producer(output_tlscli_t* self);
diff --git a/include/dnsjit/output/tlscli.lua b/include/dnsjit/output/tlscli.lua
new file mode 100644
index 0000000..e37439b
--- /dev/null
+++ b/include/dnsjit/output/tlscli.lua
@@ -0,0 +1,103 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.tlscli
+-- Simple TLS client
+-- local output = require("dnsjit.output.tlscli").new("127.0.0.1", "853")
+--
+-- Simple TLS client that attempts to do a TLS handshake (without
+-- certificate verification). It behaves the same way as tcpcli, except all
+-- the data is sent over the encrypted channel.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.tlscli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_tlscli_t"
+local output_tlscli_t = ffi.typeof(t_name)
+local Tlscli = {}
+
+-- Create a new Tlscli output.
+function Tlscli.new()
+ local self = {
+ obj = output_tlscli_t(),
+ }
+ C.output_tlscli_init(self.obj)
+ ffi.gc(self.obj, C.output_tlscli_destroy)
+ return setmetatable(self, { __index = Tlscli })
+end
+
+-- Set the timeout when producing objects.
+function Tlscli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- , perform a TLS handshake and return 0 if successful.
+function Tlscli:connect(host, port)
+ return C.output_tlscli_connect(self.obj, host, port)
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Tlscli:receive()
+ return C.output_tlscli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- The producer will wait for data and if timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- If a timeout happens during during the first stage, getting the length, it
+-- will fail and return nil.
+-- Additional calls will continue retrieving the payload.
+-- The producer returns nil on error.
+function Tlscli:produce()
+ return C.output_tlscli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Tlscli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of completely
+-- received DNS messages.
+function Tlscli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending.
+function Tlscli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Tlscli
diff --git a/include/dnsjit/output/udpcli.c b/include/dnsjit/output/udpcli.c
new file mode 100644
index 0000000..b207d9c
--- /dev/null
+++ b/include/dnsjit/output/udpcli.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/udpcli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.udpcli");
+static output_udpcli_t _defaults = {
+ LOG_T_INIT_OBJ("output.udpcli"),
+ 0, 0, -1,
+ { 0 }, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0), 0,
+ { 5, 0 }, 1
+};
+
+core_log_t* output_udpcli_log()
+{
+ return &_log;
+}
+
+void output_udpcli_init(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+}
+
+void output_udpcli_destroy(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ }
+}
+
+int output_udpcli_connect(output_udpcli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ memcpy(&self->addr, addr->ai_addr, addr->ai_addrlen);
+ self->addr_len = addr->ai_addrlen;
+ freeaddrinfo(addr);
+
+ if ((self->fd = socket(((struct sockaddr*)&self->addr)->sa_family, SOCK_DGRAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ return -2;
+ }
+
+ return 0;
+}
+
+int output_udpcli_nonblocking(output_udpcli_t* self)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ flags = fcntl(self->fd, F_GETFL);
+ if (flags != -1) {
+ flags = flags & O_NONBLOCK ? 1 : 0;
+ }
+
+ return flags;
+}
+
+int output_udpcli_set_nonblocking(output_udpcli_t* self, int nonblocking)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -1;
+ }
+
+ if (nonblocking) {
+ flags |= O_NONBLOCK;
+ self->blocking = 0;
+ } else {
+ flags &= ~O_NONBLOCK;
+ self->blocking = 1;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void _receive(output_udpcli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ for (;;) {
+ ssize_t ret = sendto(self->fd, payload + sent, len - sent, 0, (struct sockaddr*)&self->addr, self->addr_len);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_udpcli_receiver(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_udpcli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ for (;;) {
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_block(output_udpcli_t* self)
+{
+ ssize_t n;
+ struct pollfd p;
+ int to;
+ mlassert_self();
+
+ p.fd = self->fd;
+ p.events = POLLIN;
+ p.revents = 0;
+ to = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!to) {
+ to = 1;
+ }
+
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_udpcli_producer(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if (self->blocking) {
+ return (core_producer_t)_produce_block;
+ }
+ return (core_producer_t)_produce;
+}
diff --git a/include/dnsjit/output/udpcli.h b/include/dnsjit/output/udpcli.h
new file mode 100644
index 0000000..f783642
--- /dev/null
+++ b/include/dnsjit/output/udpcli.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dnsjit/core/log.h>
+#include <dnsjit/core/receiver.h>
+#include <dnsjit/core/producer.h>
+#include <dnsjit/core/object/payload.h>
+#include <dnsjit/core/timespec.h>
+
+#ifndef __dnsjit_output_udpcli_h
+#define __dnsjit_output_udpcli_h
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <dnsjit/output/udpcli.hh>
+
+#endif
diff --git a/include/dnsjit/output/udpcli.hh b/include/dnsjit/output/udpcli.hh
new file mode 100644
index 0000000..084a5b6
--- /dev/null
+++ b/include/dnsjit/output/udpcli.hh
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit 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.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_udpcli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd;
+
+ struct sockaddr_storage addr;
+ size_t addr_len;
+
+ uint8_t recvbuf[4 * 1024];
+ core_object_payload_t pkt;
+ size_t pkts_recv;
+
+ core_timespec_t timeout;
+ int8_t blocking;
+} output_udpcli_t;
+
+core_log_t* output_udpcli_log();
+
+void output_udpcli_init(output_udpcli_t* self);
+void output_udpcli_destroy(output_udpcli_t* self);
+int output_udpcli_connect(output_udpcli_t* self, const char* host, const char* port);
+int output_udpcli_nonblocking(output_udpcli_t* self);
+int output_udpcli_set_nonblocking(output_udpcli_t* self, int nonblocking);
+
+core_receiver_t output_udpcli_receiver(output_udpcli_t* self);
+core_producer_t output_udpcli_producer(output_udpcli_t* self);
diff --git a/include/dnsjit/output/udpcli.lua b/include/dnsjit/output/udpcli.lua
new file mode 100644
index 0000000..0584725
--- /dev/null
+++ b/include/dnsjit/output/udpcli.lua
@@ -0,0 +1,121 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit 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.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.udpcli
+-- Simple and dumb UDP DNS client
+-- local output = require("dnsjit.output.udpcli").new("127.0.0.1", "53")
+--
+-- Simple and rather dumb DNS client that takes any payload you give it and
+-- sends the full payload over UDP.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.udpcli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_udpcli_t"
+local output_udpcli_t = ffi.typeof(t_name)
+local Udpcli = {}
+
+-- Create a new Udpcli output.
+function Udpcli.new()
+ local self = {
+ obj = output_udpcli_t(),
+ }
+ C.output_udpcli_init(self.obj)
+ ffi.gc(self.obj, C.output_udpcli_destroy)
+ return setmetatable(self, { __index = Udpcli })
+end
+
+-- Set the timeout when producing objects.
+function Udpcli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Udpcli:connect(host, port)
+ return C.output_udpcli_connect(self.obj, host, port)
+end
+
+-- Enable (true) or disable (false) nonblocking mode and
+-- return 0 if successful, if
+-- .I bool
+-- is not specified then return if nonblocking mode is on (true) or off (false).
+function Udpcli:nonblocking(bool)
+ if bool == nil then
+ if C.output_udpcli_nonblocking(self.obj) == 1 then
+ return true
+ end
+ return false
+ elseif bool == true then
+ return C.output_udpcli_set_nonblocking(self.obj, 1)
+ else
+ return C.output_udpcli_set_nonblocking(self.obj, 0)
+ end
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Udpcli:receive()
+ return C.output_udpcli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- The producer returns nil on error.
+function Udpcli:produce()
+ return C.output_udpcli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Udpcli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of successful
+-- calls to
+-- .IR recvfrom (2)
+-- that returned data.
+function Udpcli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending or receiving.
+function Udpcli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Udpcli