From a68848db159cc1cafa82f9d383432fda459c8745 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 17 Jul 2021 09:11:16 +0200 Subject: Merging upstream version 1.2.1. Signed-off-by: Daniel Baumann --- include/dnsjit/output/dnscli.c | 888 +++++++++++++++++++++++++++++++++++++ include/dnsjit/output/dnscli.h | 38 ++ include/dnsjit/output/dnscli.hh | 72 +++ include/dnsjit/output/dnscli.lua | 187 ++++++++ include/dnsjit/output/null.lua | 31 ++ include/dnsjit/output/pcap.c | 120 +++++ include/dnsjit/output/pcap.h | 32 ++ include/dnsjit/output/pcap.hh | 42 ++ include/dnsjit/output/pcap.lua | 93 ++++ include/dnsjit/output/respdiff.c | 298 +++++++++++++ include/dnsjit/output/respdiff.h | 31 ++ include/dnsjit/output/respdiff.hh | 36 ++ include/dnsjit/output/respdiff.lua | 94 ++++ include/dnsjit/output/tcpcli.c | 381 ++++++++++++++++ include/dnsjit/output/tcpcli.h | 32 ++ include/dnsjit/output/tcpcli.hh | 51 +++ include/dnsjit/output/tcpcli.lua | 131 ++++++ include/dnsjit/output/tlscli.c | 345 ++++++++++++++ include/dnsjit/output/tlscli.h | 34 ++ include/dnsjit/output/tlscli.hh | 52 +++ include/dnsjit/output/tlscli.lua | 103 +++++ include/dnsjit/output/udpcli.c | 300 +++++++++++++ include/dnsjit/output/udpcli.h | 35 ++ include/dnsjit/output/udpcli.hh | 53 +++ include/dnsjit/output/udpcli.lua | 121 +++++ 25 files changed, 3600 insertions(+) create mode 100644 include/dnsjit/output/dnscli.c create mode 100644 include/dnsjit/output/dnscli.h create mode 100644 include/dnsjit/output/dnscli.hh create mode 100644 include/dnsjit/output/dnscli.lua create mode 100644 include/dnsjit/output/null.lua create mode 100644 include/dnsjit/output/pcap.c create mode 100644 include/dnsjit/output/pcap.h create mode 100644 include/dnsjit/output/pcap.hh create mode 100644 include/dnsjit/output/pcap.lua create mode 100644 include/dnsjit/output/respdiff.c create mode 100644 include/dnsjit/output/respdiff.h create mode 100644 include/dnsjit/output/respdiff.hh create mode 100644 include/dnsjit/output/respdiff.lua create mode 100644 include/dnsjit/output/tcpcli.c create mode 100644 include/dnsjit/output/tcpcli.h create mode 100644 include/dnsjit/output/tcpcli.hh create mode 100644 include/dnsjit/output/tcpcli.lua create mode 100644 include/dnsjit/output/tlscli.c create mode 100644 include/dnsjit/output/tlscli.h create mode 100644 include/dnsjit/output/tlscli.hh create mode 100644 include/dnsjit/output/tlscli.lua create mode 100644 include/dnsjit/output/udpcli.c create mode 100644 include/dnsjit/output/udpcli.h create mode 100644 include/dnsjit/output/udpcli.hh create mode 100644 include/dnsjit/output/udpcli.lua (limited to 'include/dnsjit/output') 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 . + */ + +#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 +#include +#include +#include +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 . + */ + +#include +#include +#include +#include +#include +#include + +#ifndef __dnsjit_output_dnscli_h +#define __dnsjit_output_dnscli_h + +#include +#include +#include +#include + +#include + +#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 . + */ + +//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 . + +-- 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 . + +-- 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 . + */ + +#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 . + */ + +#include +#include +#include + +#ifndef __dnsjit_output_pcap_h +#define __dnsjit_output_pcap_h + +#include + +#include + +#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 . + */ + +#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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/respdiff.h" +#include "core/assert.h" +#include "core/object/payload.h" + +#ifdef HAVE_LMDB_H +#include +#endif +#include +#include +#include + +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 . + */ + +#include +#include + +#ifndef __dnsjit_output_respdiff_h +#define __dnsjit_output_respdiff_h + +#include + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/tcpcli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include + +#ifndef __dnsjit_output_tcpcli_h +#define __dnsjit_output_tcpcli_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/tlscli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include + +#ifndef __dnsjit_output_tlscli_h +#define __dnsjit_output_tlscli_h + +#include + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/udpcli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include + +#ifndef __dnsjit_output_udpcli_h +#define __dnsjit_output_udpcli_h + +#include +#include + +#include + +#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 . + */ + +//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 . + +-- 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 -- cgit v1.2.3