/*
* dnstap/unbound-dnstap-socket.c - debug program that listens for DNSTAP logs.
*
* Copyright (c) 2020, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** listen backlog on TCP connections for dnstap logs */
#define LISTEN_BACKLOG 16
/** usage information for streamtcp */
static void usage(char* argv[])
{
printf("usage: %s [options]\n", argv[0]);
printf(" Listen to dnstap messages\n");
printf("stdout has dnstap log, stderr has verbose server log\n");
printf("-u <socketpath> listen to unix socket with this file name\n");
printf("-s <serverip[@port]> listen for TCP on the IP and port\n");
printf("-t <serverip[@port]> listen for TLS on IP and port\n");
printf("-x <server.key> server key file for TLS service\n");
printf("-y <server.pem> server cert file for TLS service\n");
printf("-z <verify.pem> cert file to verify client connections\n");
printf("-l long format for DNS printout\n");
printf("-v more verbose log output\n");
printf("-c internal unit test and exit\n");
printf("-h this help text\n");
exit(1);
}
/** long format option, for multiline printout per message */
static int longformat = 0;
struct tap_socket_list;
struct tap_socket;
/** main tap callback data */
struct main_tap_data {
/** the event base (to loopexit) */
struct ub_event_base* base;
/** the list of accept sockets */
struct tap_socket_list* acceptlist;
};
/* list of data */
struct tap_data_list {
/** next in list */
struct tap_data_list* next;
/** the data */
struct tap_data* d;
};
/** tap callback variables */
struct tap_data {
/** the fd */
int fd;
/** the ub event */
struct ub_event* ev;
/** the SSL for TLS streams */
SSL* ssl;
/** is the ssl handshake done */
int ssl_handshake_done;
/** we are briefly waiting to write (in the struct event) */
int ssl_brief_write;
/** string that identifies the socket (or NULL), like IP address */
char* id;
/** have we read the length, and how many bytes of it */
int len_done;
/** have we read the data, and how many bytes of it */
size_t data_done;
/** are we reading a control frame */
int control_frame;
/** are we bi-directional (if false, uni-directional) */
int is_bidirectional;
/** data of the frame */
uint8_t* frame;
/** length of this frame */
size_t len;
/** back pointer to the tap_data_list entry;
* used to NULL the forward pointer to this data
* when this data is freed. */
struct tap_data_list* data_list;
};
/** list of sockets */
struct tap_socket_list {
/** next in list */
struct tap_socket_list* next;
/** the socket */
struct tap_socket* s;
};
/** tap socket */
struct tap_socket {
/** fd of socket */
int fd;
/** the event for it */
struct ub_event *ev;
/** has the event been added */
int ev_added;
/** the callback, for the event, ev_cb(fd, bits, arg) */
void (*ev_cb)(int, short, void*);
/** data element, (arg for the tap_socket struct) */
void* data;
/** socketpath, if this is an AF_LOCAL socket */
char* socketpath;
/** IP, if this is a TCP socket */
char* ip;
/** for a TLS socket, the tls context */
SSL_CTX* sslctx;
/** dumb way to deal with memory leaks:
* tap_data was only freed on errors and not during exit leading to
* false positives when testing for memory leaks. */
struct tap_data_list* data_list;
};
/** try to delete tail entries from the list if all of them have no data */
static void tap_data_list_try_to_free_tail(struct tap_data_list* list)
{
struct tap_data_list* current = list;
log_assert(!list->d);
if(!list->next) /* we are the last, we can't remove ourselves */
return;
list = list->next;
while(list) {
if(list->d) /* a tail entry still has data; return */
return;
list = list->next;
}
/* keep the next */
list = current->next;
/* the tail will be removed; but not ourselves */
current->next = NULL;
while(list) {
current = list;
list = list->next;
free(current);
}
}
/** setup tcp accept socket on IP string */
static int make_tcp_accept(char* ip)
{
#ifdef SO_REUSEADDR
int on = 1;
#endif
struct sockaddr_storage addr;
socklen_t len;
int s;
memset(&addr, 0, sizeof(addr));
len = (socklen_t)sizeof(addr);
if(!extstrtoaddr(ip, &addr, &len, UNBOUND_DNS_PORT)) {
log_err("could not parse IP '%s'", ip);
return -1;
}
/* define routine for have_ssl only to avoid unused function warning */
#ifdef HAVE_SSL
/** set to wait briefly for a write event, for one event call */
static void tap_enable_brief_write(struct tap_data* data)
{
ub_event_del(data->ev);
ub_event_del_bits(data->ev, UB_EV_READ);
ub_event_add_bits(data->ev, UB_EV_WRITE);
if(ub_event_add(data->ev, NULL) != 0)
log_err("could not ub_event_add in tap_enable_brief_write");
data->ssl_brief_write = 1;
}
#endif /* HAVE_SSL */
/* define routine for have_ssl only to avoid unused function warning */
#ifdef HAVE_SSL
/** stop the brief wait for a write event. back to reading. */
static void tap_disable_brief_write(struct tap_data* data)
{
ub_event_del(data->ev);
ub_event_del_bits(data->ev, UB_EV_WRITE);
ub_event_add_bits(data->ev, UB_EV_READ);
if(ub_event_add(data->ev, NULL) != 0)
log_err("could not ub_event_add in tap_disable_brief_write");
data->ssl_brief_write = 0;
}
#endif /* HAVE_SSL */
#ifdef HAVE_SSL
/** receive bytes over ssl stream, prints errors if bad,
* returns 0: closed/error, -1: continue, >0 number of bytes */
static ssize_t ssl_read_bytes(struct tap_data* data, void* buf, size_t len)
{
int r;
ERR_clear_error();
r = SSL_read(data->ssl, buf, len);
if(r <= 0) {
int want = SSL_get_error(data->ssl, r);
if(want == SSL_ERROR_ZERO_RETURN) {
/* closed */
if(verbosity) log_info("dnstap client stream closed from %s",
(data->id?data->id:""));
return 0;
} else if(want == SSL_ERROR_WANT_READ) {
/* continue later */
return -1;
} else if(want == SSL_ERROR_WANT_WRITE) {
/* set to briefly write */
tap_enable_brief_write(data);
return -1;
} else if(want == SSL_ERROR_SYSCALL) {
#ifdef ECONNRESET
if(errno == ECONNRESET && verbosity < 2)
return 0; /* silence reset by peer */
#endif
if(errno != 0)
log_err("SSL_read syscall: %s",
strerror(errno));
if(verbosity) log_info("dnstap client stream closed from %s",
(data->id?data->id:""));
return 0;
}
log_crypto_err_io("could not SSL_read", want);
if(verbosity) log_info("dnstap client stream closed from %s",
(data->id?data->id:""));
return 0;
}
return r;
}
#endif /* HAVE_SSL */
/** receive bytes on the tap connection, prints errors if bad,
* returns 0: closed/error, -1: continue, >0 number of bytes */
static ssize_t tap_receive(struct tap_data* data, void* buf, size_t len)
{
#ifdef HAVE_SSL
if(data->ssl)
return ssl_read_bytes(data, buf, len);
#endif
return receive_bytes(data, data->fd, buf, len);
}
/** reply with ACCEPT control frame to bidirectional client,
* returns 0 on error */
static int reply_with_accept(struct tap_data* data)
{
#ifdef USE_DNSTAP
/* len includes the escape and framelength */
size_t len = 0;
void* acceptframe = fstrm_create_control_frame_accept(
DNSTAP_CONTENT_TYPE, &len);
if(!acceptframe) {
log_err("out of memory");
return 0;
}
#ifdef HAVE_SSL
/** check SSL peer certificate, return 0 on fail */
static int tap_check_peer(struct tap_data* data)
{
if((SSL_get_verify_mode(data->ssl)&SSL_VERIFY_PEER)) {
/* verification */
if(SSL_get_verify_result(data->ssl) == X509_V_OK) {
#ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
X509* x = SSL_get1_peer_certificate(data->ssl);
#else
X509* x = SSL_get_peer_certificate(data->ssl);
#endif
if(!x) {
if(verbosity) log_info("SSL connection %s"
" failed no certificate", data->id);
return 0;
}
if(verbosity)
log_cert(VERB_ALGO, "peer certificate", x);
#ifdef HAVE_SSL_GET0_PEERNAME
if(SSL_get0_peername(data->ssl)) {
if(verbosity) log_info("SSL connection %s "
"to %s authenticated", data->id,
SSL_get0_peername(data->ssl));
} else {
#endif
if(verbosity) log_info("SSL connection %s "
"authenticated", data->id);
#ifdef HAVE_SSL_GET0_PEERNAME
}
#endif
X509_free(x);
} else {
#ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
X509* x = SSL_get1_peer_certificate(data->ssl);
#else
X509* x = SSL_get_peer_certificate(data->ssl);
#endif
if(x) {
if(verbosity)
log_cert(VERB_ALGO, "peer certificate", x);
X509_free(x);
}
if(verbosity) log_info("SSL connection %s failed: "
"failed to authenticate", data->id);
return 0;
}
} else {
/* unauthenticated, the verify peer flag was not set
* in ssl when the ssl object was created from ssl_ctx */
if(verbosity) log_info("SSL connection %s", data->id);
}
return 1;
}
#endif /* HAVE_SSL */
#ifdef HAVE_SSL
/** perform SSL handshake, return 0 to wait for events, 1 if done */
static int tap_handshake(struct tap_data* data)
{
int r;
if(data->ssl_brief_write) {
/* write condition has been satisfied, back to reading */
tap_disable_brief_write(data);
}
if(data->ssl_handshake_done)
return 1;
/* we want to read the full length now */
if(data->data_done < data->len) {
ssize_t r = tap_receive(data, data->frame + data->data_done,
data->len - data->data_done);
if(verbosity>=4) log_info("f recv %d", (int)r);
if(r == 0) {
/* closed or error */
tap_data_free(data, 1);
return;
} else if(r == -1) {
/* continue later */
return;
}
data->data_done += r;
if(data->data_done < data->len)
return; /* continue later */
}
/* we are done with a frame */
if(verbosity>=3) log_info("received %sframe len %d",
(data->control_frame?"control ":""), (int)data->len);
#ifdef USE_DNSTAP
if(data->control_frame)
log_control_frame(data->frame, data->len);
else log_data_frame(data->frame, data->len);
#endif
setup_local_list(maindata, local_list);
setup_tcp_list(maindata, tcp_list);
setup_tls_list(maindata, tls_list, server_key, server_cert,
verifypem);
if(!tap_socket_list_addevs(maindata->acceptlist, base))
fatal_exit("could not setup accept events");
if(verbosity) log_info("start of service");
ub_event_base_dispatch(base);
sig_base = NULL;
if(verbosity) log_info("end of service");
tap_socket_list_delete(maindata->acceptlist);
ub_event_base_free(base);
free(maindata);
}
/* internal unit tests */
static int internal_unittest()
{
/* unit test tap_data_list_try_to_free_tail() */
#define unit_tap_datas_max 5
struct tap_data* datas[unit_tap_datas_max];
struct tap_data_list* list;
struct tap_socket* socket = calloc(1, sizeof(*socket));
size_t i = 0;
log_assert(socket);
log_assert(unit_tap_datas_max>2); /* needed for the test */
for(i=0; i<unit_tap_datas_max; i++) {
datas[i] = calloc(1, sizeof(struct tap_data));
log_assert(datas[i]);
log_assert(tap_data_list_insert(&socket->data_list, datas[i]));
}
/* sanity base check */
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==unit_tap_datas_max);
/* Free the last data, tail cannot be erased */
list = socket->data_list;
while(list->next) list = list->next;
free(list->d);
list->d = NULL;
tap_data_list_try_to_free_tail(list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==unit_tap_datas_max);
/* Free the third to last data, tail cannot be erased */
list = socket->data_list;
for(i=0; i<unit_tap_datas_max-3; i++) list = list->next;
free(list->d);
list->d = NULL;
tap_data_list_try_to_free_tail(list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==unit_tap_datas_max);
/* Free the second to last data, try to remove tail from the third
* again, tail (last 2) should be removed */
list = socket->data_list;
for(i=0; i<unit_tap_datas_max-2; i++) list = list->next;
free(list->d);
list->d = NULL;
list = socket->data_list;
while(list->d) list = list->next;
tap_data_list_try_to_free_tail(list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==unit_tap_datas_max-2);
/* Free all the remaining data, try to remove tail from the start,
* only the start should remain */
list = socket->data_list;
while(list) {
free(list->d);
list->d = NULL;
list = list->next;
}
tap_data_list_try_to_free_tail(socket->data_list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==1);
/* clean up */
tap_data_list_delete(socket->data_list);
free(socket);
/* Start again. Add two elements */
socket = calloc(1, sizeof(*socket));
log_assert(socket);
for(i=0; i<2; i++) {
datas[i] = calloc(1, sizeof(struct tap_data));
log_assert(datas[i]);
log_assert(tap_data_list_insert(&socket->data_list, datas[i]));
}
/* sanity base check */
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==2);
/* Free the last data, tail cannot be erased */
list = socket->data_list;
while(list->next) list = list->next;
free(list->d);
list->d = NULL;
tap_data_list_try_to_free_tail(list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==2);
/* clean up */
tap_data_list_delete(socket->data_list);
free(socket);
/** getopt global, in case header files fail to declare it. */
extern int optind;
/** getopt global, in case header files fail to declare it. */
extern char* optarg;
/***--- definitions to make fptr_wlist work. ---***/
/* These are callbacks, similar to smallapp callbacks, except the debug
* tool callbacks are not in it */
struct tube;
struct query_info;
#include "util/data/packed_rrset.h"
#include "daemon/worker.h"
#include "daemon/remote.h"
#include "util/fptr_wlist.h"
#include "libunbound/context.h"
/** keep track of lock id in lock-verify application */
struct order_id {
/** the thread id that created it */
int thr;
/** the instance number of creation */
int instance;
};