unbound/testcode/doqclient.c
Wouter Wijngaards 3d89c26d32
DNSoverQUIC (#871)
* - dnsoverquic, configure --with-libngtcp2 option.

* - dnsoverquic, create comm_point for doq and receive cmsg local address.

* - dnsoverquic, less obtrusive debug.

* - dnsoverquic, log and fix local port number. Neater subroutines and ifdefs.

* - dnsoverquic, add testcode/doqclient.

* - dnsoverquic, review fixes on doqclient.

* - dnsoverquic, fix unit test testbound link.

* - dnsoverquic, parse query in doqclient.

* - dnsoverquic, link with libngtcp2_crypto_openssl and code for doqclient.

* - dnsoverquic, random routine for doqclient and fix ngaddr allocation, and
  check ub_initstate return.

* - dnsoverquic, fix doqclient free of allocated ngaddr addresses.

* - dnsoverquic, enable debug output with -v for doqclient.

* - dnsoverquic, create and set TLS object and TLS context in doqclient.

* - dnsoverquic, work on quic tls context in doqclient.

* - dnsoverquic, set default dnsoverquic port to the standardized 853 port.

* - dnsoverquic, remove debug comment.

* - dnsoverquic, dns-over-quic quic-port: 853 config option.

* - dnsoverquic, log type of interface created at start of unbound.

* - dnsoverquic, log type of no tls https as https when interface is created.

* - dnsoverquic, setup client quic tls methods.

* - dnsoverquic, event work in doqclient.

* - dnsoverquic, explain in documentation that QUIC uses UDP.

* - dnsoverquic, make doqclient exit.

* - dnsoverquic, doqclient cleanup run routine.

* - dnsoverquic, doqclient code nicer.

* - dnsoverquic, doqclient read and timer.

* - dnsoverquic, doqclient write work.

* - dnsoverquic, review fixes.

* - dnsoverquic, detect openssl quic support at configure time.

* - dnsoverquic, do not allow QUIC on port 53 to stop confusion of DoQ and DNS.

* - dnsoverquic, in doqclient, when idle close is returned, drop the connection
  without calling ngtcp2_conn_write_connection_close.

* - dnsoverquic, in doqclient, log callbacks.

* - dnsoverquic, in doqclient add extend_max_local_streams_bidi callback.

* - dnsoverquic, in doqclient add client query lists.

* - dnsoverquic, in doqclient, code cleaner, log text nicer.

* - dnsoverquic, in doqclient, work on write_streams.

* - dnsoverquic, in doqclient, use signed int for stream_id, work on the
  ngtcp2_recv_stream_data callback.

* - dnsoverquic, in doqclient, print result and fixes for recv data.

* - dnsoverquic, in doqclient, add the event callbacks to fptr wlist.

* - dnsoverquic, in doqclient, when already expired, use zero timeout timer.

* - dnsoverquic, in doqclient, ignore unused return codes from
  ngtcp2_conn_writev_stream.

* - dnsoverquic, add doqclient event functions to the unbound-dnstap-socket
  test tool for linking.

* - dnsoverquic, in doqclient, fix multiple operands for the commandline.
  neater dns message output.

* - dnsoverquic, in doqclient, store packet when write blocks and try later.

* - dnsoverquic, in doqclient, limit number of packets and number of bytes sent.

* - dnsoverquic, in doqclient, better size estimate for outgoing packet.

* - dnsoverquic, in doqclient, fix that already written next packet is not
  counted for data length to send.

* - dnsoverquic, in doqclient, early data transmission and session resumption.

* - dnsoverquic, send version negotiation packet.

* - dnsoverquic, send retry and accept the connection.

* - dnsoverquic, storage structures.

* - dnsoverquic, doq connection setup.

* - dnsoverquic, neater code layout for new conn. Fix verbosity of log print.

* - dnsoverquic, doq conn callback functions.

* - dnsoverquic, doq_fill_rand routine in header file.

* - dnsoverquic, keep track of connection ids.

* - dnsoverquic, get_new_connection_id callback.

* - dnsoverquic, create doq_conid tree.

* - dnsoverquic, settings for server connection.

* - dnsoverquic, tls context.

* - dnsoverquic, sendmsg error handling.

* - dnsoverquic, neat code.

* - dnsoverquic, track doq connection last error.

* - dnsoverquic, neater packet address parameters.

* - dnsoverquic, fix uninitialized bytes in msg control in doq sendmsg, and
  fix tree cleanup of conid tree.

* - dnsoverquic, better usage text for doqclient.

* - dnsoverquic, neat code.

* - dnsoverquic, connection receive packet handling.

* - dnsoverquic, debug output.

* - dnsoverquic, debug switched meaning of scid and dcid gives
  ERR_TRANSPORT_PARAM.

* - dnsoverquic, remove debug output.

* - dnsoverquic, connection delete routine and error from connection read in
  more detail with less clutter.

* - dnsoverquic, write to stream, and receive stream data, log packet.

* - dnsoverquic, alpn set up.

* - dnsoverquic, connection close.

* - dnsoverquic, doq_table and locks.

* - dnsoverquic, fix tests.

* - dnsoverquic, better locking.

* - dnsoverquic, doq_stream.

* - dnsoverquic, remove compile warning.

* - dnsoverquic, doq_stream receive data.

* - dnsoverquic, fixes for locks and keep length bytes allocated.

* - dnsoverquic, lock connection on initial insertion.

* - dnsoverquic, reply information, and reply buffer.

* - dnsoverquic, reply info from cache, local-zone and recursion lookups.

* - dnsoverquic, spelling in comment about buffer storage.

* - dnsoverquic, stream write list and doqclient fixes to exit and printout.

* - dnsoverquic, doqclient -q option for short printout.

* - dnsoverquic, unit test with local data reply.

* - dnsoverquic, write connection and write event is set.

* - dnsoverquic, neater logging for write event connection stream writes.

* - dnsoverquic, log remote connection when the streams are written for it.

* - dnsoverquic, better threaded use, threads can write to doq connections at
  the same time.

* - dnsoverquic, unit test for the calculation of connection size with a query.

* - dnsoverquic, use less memory per connection.

* - dnsoverquic, remove unit test output.

* - dnsoverquic, add MSG_DONTWAIT so that there is no mistakenly blocking
  socket operations.

* - dnsoverquic, doqclient logs address on connection failures.

* - dnsoverquic, compat code for clock get time routine.

* - dnsoverquic, use skip_test for doq unit test.

* - dnsoverquic, fixes for proxyprotocol, use remote_addr and set proxyprotocol
  disabled on the doq connection.

* - dnsoverquic, doqclient sets log identity to its name, instead of "unbound".

* - dnsoverquic, handle blocked udp packet writes.

* - dnsoverquic, fix function documentation for verbose_print_addr from
  services/listen_dnsport.c.

* - dnsoverquic, fix doq_conn lock protection. The checklock allows to set
  the output file name, and doqclient uses that. Print place of lock_protect.

* - dnsoverquic, neater buffer clear when write of blocked packet fails, make
  sure that memory area does not overlap for blocked packet addresses when
  write of blocked packet fails, and size blocked packet buffer to the pkt buf.

* - dnsoverquic, move lock check after the test to test script in doq test.

* - dnsoverquic, the doq test uses valgrind when enabled.

* - dnsoverquic, git ignore the doqclient test.

* - dnsoverquic, limit the buffer for packets to max packet size with some more.

* - dnsoverquic, spelling fix.

* - dnsoverquic, timer work, structure and adds and deletes.

* - dnsoverquic, timer_tree uses table.lock.

* - dnsoverquic, fix timer tree remove and spelling in header file comment.

* - dnsoverquic, fix testbound for timer compare function linkage.

* - dnsoverquic, timer set add debug output.

* - dnsoverquic, doq_conn_check_timer function.

* - dnsoverquic, doq_done_setup_timer_and_write function.

* - dnsoverquic, fix that doq conn is not deleted whilst editing write and timer.

* - dnsoverquic, Fix #861 make ERROR netevent.h:1073:32: error: field 'blocked_pkt_pi' has incomplete type

* - dnsoverquic, timer element has timeout setup when socket callback complete.

* - dnsoverquic, fix unit test compile.

* - dnsoverquic, timer callback routine, handle timeout and close and delete the
  connection if necessary.

* - dnsoverquic, timer pickup stops at current time.

* - dnsoverquic, timer comparable with the event base time.

* - dnsoverquic, erase marked time when timer disabled.

* - dnsoverquic, fix timer to set correctly and lock popped write connection
  early, before it is modified.

* - dnsoverquic, fix to unlock connection lock when it is unlinked and deleted.

* - dnsoverquic, fix to unlock connection lock when it is deleted because it is
  a duplicate connection.

* - dnsoverquic, fix that doq timer is not disabled when not set.

* - dnsoverquic, quic-size: 8m maximum number of bytes for QUIC buffers.

* - dnsoverquic, flex and bison.

* - dnsoverquic, quic-size turn away new connections when full.

* - dnsoverquic, doqclient outputs stream reset information.

* - dnsoverquic, detect stream close and reset.

* - dnsoverquic, free stream buffers when data is acked and stream is closed.

* - dnsoverquic, delete stream when closed. Unlink it. Allow stream_id 4 as first.

* - dnsoverquic, stats output for mem.quic and num.query.quic.

* - dnsoverquic, review fix.

* - dnsoverquic, fix when compiled without ngtcp2.

* - dnsoverquic, fix to detect ngtcp2_crypto_quictls for openssl crypto, after
  change in libngtcp2.

* - dnsoverquic, fix for newer ngtcp2 versions. detect ngtcp2_ccerr_default,
  ngtcp2/ngtcp2_crypto_quictls.h, struct ngtcp2_pkt_hd.tokenlen,
  struct ngtcp2_settings.tokenlen and struct ngtcp2_version_cid.

* - dnsoverquic, fix for newer ngtcp2 version, detect number of arguments for
  ngtcp2_conn_shutdown_stream.

* - dnsoverquic, fix for newer ngtcp2.

* - dnsoverquic, use the functions from util/timeval_func.h.

* - dnsoverquic, fix in doqclient only write transport parameters once.

* - dnsoverquic, debug log output removed.

* - dnsoverquic, fix in doqclient to work with renamed NGTCP2_CC_ALGO_BBR_V2
  from ngtcp2.

* - dnsoverquic, fix to check in doq_server_socket_create that tls-service-key
  and tls-service-pem have a value.

* - dnsoverquic, fix to error when doq_server_socket_create fails.

* - dnsoverquic, improve linebreaks in configparser additions.

* - dnsoverquic, fix port from interface pickup after main branch change.

* Fix getting user data from SSL, fix calloc warning.

* Fix fwrite return value check in doqclient

* - timeval_substruct from timeval_func.h
- lock_protect also for HAVE_NGTCP2_CCERR_DEFAULT
- fix doq logging for inet_ntop failures

* - memset for consistency
- no value returned from msghdr_get_ecn when S_SPLINT_S is defined

* - dnsoverquic, rerun autoconf.

---------

Co-authored-by: Yorgos Thessalonikefs <yorgos@nlnetlabs.nl>
2024-10-09 10:32:03 +02:00

2685 lines
76 KiB
C

/*
* testcode/doqclient.c - debug program. Perform multiple DNS queries using DoQ.
*
* Copyright (c) 2022, 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.
*/
/**
* \file
*
* Simple DNS-over-QUIC client. For testing and debugging purposes.
* No authentication of TLS cert.
*/
#include "config.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_NGTCP2
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#ifdef HAVE_NGTCP2_NGTCP2_CRYPTO_QUICTLS_H
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#else
#include <ngtcp2/ngtcp2_crypto_openssl.h>
#endif
#include <openssl/ssl.h>
#include <openssl/rand.h>
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <sys/time.h>
#include "util/locks.h"
#include "util/net_help.h"
#include "sldns/sbuffer.h"
#include "sldns/str2wire.h"
#include "sldns/wire2str.h"
#include "util/data/msgreply.h"
#include "util/data/msgencode.h"
#include "util/data/msgparse.h"
#include "util/data/dname.h"
#include "util/random.h"
#include "util/ub_event.h"
struct doq_client_stream_list;
struct doq_client_stream;
/** the local client data for the DoQ connection */
struct doq_client_data {
/** file descriptor */
int fd;
/** the event base for the events */
struct ub_event_base* base;
/** the ub event */
struct ub_event* ev;
/** the expiry timer */
struct ub_event* expire_timer;
/** is the expire_timer added */
int expire_timer_added;
/** the ngtcp2 connection information */
struct ngtcp2_conn* conn;
/** random state */
struct ub_randstate* rnd;
/** server connected to as a string */
const char* svr;
/** the static secret */
uint8_t* static_secret_data;
/** the static secret size */
size_t static_secret_size;
/** destination address sockaddr */
struct sockaddr_storage dest_addr;
/** length of dest addr */
socklen_t dest_addr_len;
/** local address sockaddr */
struct sockaddr_storage local_addr;
/** length of local addr */
socklen_t local_addr_len;
/** SSL context */
SSL_CTX* ctx;
/** SSL object */
SSL* ssl;
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
/** the connection reference for ngtcp2_conn and userdata in ssl */
struct ngtcp2_crypto_conn_ref conn_ref;
#endif
/** the quic version to use */
uint32_t quic_version;
/** the last error */
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
struct ngtcp2_ccerr ccerr;
#else
struct ngtcp2_connection_close_error last_error;
#endif
/** the recent tls alert error code */
uint8_t tls_alert;
/** the buffer for packet operations */
struct sldns_buffer* pkt_buf;
/** The list of queries to start. They have no stream associated.
* Once they do, they move to the send list. */
struct doq_client_stream_list* query_list_start;
/** The list of queries to send. They have a stream, and they are
* sending data. Data could also be received, like errors. */
struct doq_client_stream_list* query_list_send;
/** The list of queries to receive. They have a stream, and the
* send is done, it is possible to read data. */
struct doq_client_stream_list* query_list_receive;
/** The list of queries that are stopped. They have no stream
* active any more. Write and read are done. The query is done,
* and it may be in error and then have no answer or partial answer. */
struct doq_client_stream_list* query_list_stop;
/** is there a blocked packet in the blocked_pkt buffer */
int have_blocked_pkt;
/** store blocked packet, a packet that could not be sent on the
* nonblocking socket. */
struct sldns_buffer* blocked_pkt;
/** ecn info for the blocked packet */
struct ngtcp2_pkt_info blocked_pkt_pi;
/** the congestion control algorithm */
ngtcp2_cc_algo cc_algo;
/** the transport parameters file, for early data transmission */
const char* transport_file;
/** the tls session file, for session resumption */
const char* session_file;
/** if early data is enabled for the connection */
int early_data_enabled;
/** how quiet is the output */
int quiet;
/** the configured port for the destination */
int port;
};
/** the local client stream list, for appending streams to */
struct doq_client_stream_list {
/** first and last members of the list */
struct doq_client_stream* first, *last;
};
/** the local client data for a DoQ stream */
struct doq_client_stream {
/** next stream in list, and prev in list */
struct doq_client_stream* next, *prev;
/** the data buffer */
uint8_t* data;
/** length of the data buffer */
size_t data_len;
/** if the client query has a stream, that is active, associated with
* it. The stream_id is in stream_id. */
int has_stream;
/** the stream id */
int64_t stream_id;
/** data written position */
size_t nwrite;
/** the data length for write, in network format */
uint16_t data_tcplen;
/** if the write of the query data is done. That means the
* write channel has FIN, is closed for writing. */
int write_is_done;
/** data read position */
size_t nread;
/** the answer length, in network byte order */
uint16_t answer_len;
/** the answer buffer */
struct sldns_buffer* answer;
/** the answer is complete */
int answer_is_complete;
/** the query has an error, it has no answer, or no complete answer */
int query_has_error;
/** if the query is done */
int query_is_done;
};
#ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
/** the quic method struct, must remain valid during the QUIC connection. */
static SSL_QUIC_METHOD quic_method;
#endif
/** Get the connection ngtcp2_conn from the ssl app data
* ngtcp2_crypto_conn_ref */
static ngtcp2_conn* conn_ref_get_conn(ngtcp2_crypto_conn_ref* conn_ref)
{
struct doq_client_data* data = (struct doq_client_data*)
conn_ref->user_data;
return data->conn;
}
static void
set_app_data(SSL* ssl, struct doq_client_data* data)
{
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
data->conn_ref.get_conn = &conn_ref_get_conn;
data->conn_ref.user_data = data;
SSL_set_app_data(ssl, &data->conn_ref);
#else
SSL_set_app_data(ssl, data);
#endif
}
static struct doq_client_data*
get_app_data(SSL* ssl)
{
struct doq_client_data* data;
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
data = (struct doq_client_data*)((struct ngtcp2_crypto_conn_ref*)
SSL_get_app_data(ssl))->user_data;
#else
data = (struct doq_client_data*) SSL_get_app_data(ssl);
#endif
return data;
}
/** write handle routine */
static void on_write(struct doq_client_data* data);
/** update the timer */
static void update_timer(struct doq_client_data* data);
/** disconnect we are done */
static void disconnect(struct doq_client_data* data);
/** fetch and write the transport file */
static void early_data_write_transport(struct doq_client_data* data);
/** usage of doqclient */
static void usage(char* argv[])
{
printf("usage: %s [options] name type class ...\n", argv[0]);
printf(" sends the name-type-class queries over "
"DNS-over-QUIC.\n");
printf("-s server IP address to send the queries to, "
"default: 127.0.0.1\n");
printf("-p Port to connect to, default: %d\n",
UNBOUND_DNS_OVER_QUIC_PORT);
printf("-v verbose output\n");
printf("-q quiet, short output of answer\n");
printf("-x file transport file, for read/write of transport parameters.\n\t\tIf it exists, it is used to send early data. It is then\n\t\twritten to contain the last used transport parameters.\n\t\tAlso -y must be enabled for early data to succeed.\n");
printf("-y file session file, for read/write of TLS session. If it exists,\n\t\tit is used for TLS session resumption. It is then written\n\t\tto contain the last session used.\n\t\tOn its own, without also -x, resumes TLS session.\n");
printf("-h This help text\n");
exit(1);
}
/** get the dest address */
static void
get_dest_addr(struct doq_client_data* data, const char* svr, int port)
{
if(!ipstrtoaddr(svr, port, &data->dest_addr, &data->dest_addr_len)) {
printf("fatal: bad server specs '%s'\n", svr);
exit(1);
}
}
/** open UDP socket to svr */
static int
open_svr_udp(struct doq_client_data* data)
{
int fd = -1;
int r;
fd = socket(addr_is_ip6(&data->dest_addr, data->dest_addr_len)?
PF_INET6:PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket() error");
exit(1);
}
r = connect(fd, (struct sockaddr*)&data->dest_addr,
data->dest_addr_len);
if(r < 0 && r != EINPROGRESS) {
perror("connect() error");
exit(1);
}
fd_set_nonblock(fd);
return fd;
}
/** get the local address of the connection */
static void
get_local_addr(struct doq_client_data* data)
{
memset(&data->local_addr, 0, sizeof(data->local_addr));
data->local_addr_len = (socklen_t)sizeof(data->local_addr);
if(getsockname(data->fd, (struct sockaddr*)&data->local_addr,
&data->local_addr_len) == -1) {
perror("getsockname() error");
exit(1);
}
log_addr(1, "local_addr", &data->local_addr, data->local_addr_len);
log_addr(1, "dest_addr", &data->dest_addr, data->dest_addr_len);
}
static sldns_buffer*
make_query(char* qname, char* qtype, char* qclass)
{
struct query_info qinfo;
struct edns_data edns;
sldns_buffer* buf = sldns_buffer_new(65553);
if(!buf) fatal_exit("out of memory");
qinfo.qname = sldns_str2wire_dname(qname, &qinfo.qname_len);
if(!qinfo.qname) {
printf("cannot parse query name: '%s'\n", qname);
exit(1);
}
qinfo.qtype = sldns_get_rr_type_by_name(qtype);
qinfo.qclass = sldns_get_rr_class_by_name(qclass);
qinfo.local_alias = NULL;
qinfo_query_encode(buf, &qinfo); /* flips buffer */
free(qinfo.qname);
sldns_buffer_write_u16_at(buf, 0, 0x0000);
sldns_buffer_write_u16_at(buf, 2, BIT_RD);
memset(&edns, 0, sizeof(edns));
edns.edns_present = 1;
edns.bits = EDNS_DO;
edns.udp_size = 4096;
if(sldns_buffer_capacity(buf) >=
sldns_buffer_limit(buf)+calc_edns_field_size(&edns))
attach_edns_record(buf, &edns);
return buf;
}
/** create client stream structure */
static struct doq_client_stream*
client_stream_create(struct sldns_buffer* query_data)
{
struct doq_client_stream* str = calloc(1, sizeof(*str));
if(!str)
fatal_exit("calloc failed: out of memory");
str->data = memdup(sldns_buffer_begin(query_data),
sldns_buffer_limit(query_data));
if(!str->data)
fatal_exit("alloc data failed: out of memory");
str->data_len = sldns_buffer_limit(query_data);
str->stream_id = -1;
return str;
}
/** free client stream structure */
static void
client_stream_free(struct doq_client_stream* str)
{
if(!str)
return;
free(str->data);
sldns_buffer_free(str->answer);
free(str);
}
/** setup the stream to start the write process */
static void
client_stream_start_setup(struct doq_client_stream* str, int64_t stream_id)
{
str->has_stream = 1;
str->stream_id = stream_id;
str->nwrite = 0;
str->nread = 0;
str->answer_len = 0;
str->query_is_done = 0;
str->answer_is_complete = 0;
str->query_has_error = 0;
if(str->answer) {
sldns_buffer_free(str->answer);
str->answer = NULL;
}
}
/** Return string for log purposes with query name. */
static char*
client_stream_string(struct doq_client_stream* str)
{
char* s;
size_t dname_len;
char dname[256], tpstr[32], result[256+32+16];
uint16_t tp;
if(str->data_len <= LDNS_HEADER_SIZE) {
s = strdup("query_with_no_question");
if(!s)
fatal_exit("strdup failed: out of memory");
return s;
}
dname_len = dname_valid(str->data+LDNS_HEADER_SIZE,
str->data_len-LDNS_HEADER_SIZE);
if(!dname_len) {
s = strdup("query_dname_not_valid");
if(!s)
fatal_exit("strdup failed: out of memory");
return s;
}
(void)sldns_wire2str_dname_buf(str->data+LDNS_HEADER_SIZE, dname_len,
dname, sizeof(dname));
tp = sldns_wirerr_get_type(str->data+LDNS_HEADER_SIZE,
str->data_len-LDNS_HEADER_SIZE, dname_len);
(void)sldns_wire2str_type_buf(tp, tpstr, sizeof(tpstr));
snprintf(result, sizeof(result), "%s %s", dname, tpstr);
s = strdup(result);
if(!s)
fatal_exit("strdup failed: out of memory");
return s;
}
/** create query stream list */
static struct doq_client_stream_list*
stream_list_create(void)
{
struct doq_client_stream_list* list = calloc(1, sizeof(*list));
if(!list)
fatal_exit("calloc failed: out of memory");
return list;
}
/** free the query stream list */
static void
stream_list_free(struct doq_client_stream_list* list)
{
struct doq_client_stream* str;
if(!list)
return;
str = list->first;
while(str) {
struct doq_client_stream* next = str->next;
client_stream_free(str);
str = next;
}
free(list);
}
/** append item to list */
static void
stream_list_append(struct doq_client_stream_list* list,
struct doq_client_stream* str)
{
if(list->last) {
str->prev = list->last;
list->last->next = str;
} else {
str->prev = NULL;
list->first = str;
}
str->next = NULL;
list->last = str;
}
/** delete the item from the list */
static void
stream_list_delete(struct doq_client_stream_list* list,
struct doq_client_stream* str)
{
if(str->next) {
str->next->prev = str->prev;
} else {
list->last = str->prev;
}
if(str->prev) {
str->prev->next = str->next;
} else {
list->first = str->next;
}
str->prev = NULL;
str->next = NULL;
}
/** move the item from list1 to list2 */
static void
stream_list_move(struct doq_client_stream* str,
struct doq_client_stream_list* list1,
struct doq_client_stream_list* list2)
{
stream_list_delete(list1, str);
stream_list_append(list2, str);
}
/** allocate stream data buffer, then answer length is complete */
static void
client_stream_datalen_complete(struct doq_client_stream* str)
{
verbose(1, "answer length %d", (int)ntohs(str->answer_len));
str->answer = sldns_buffer_new(ntohs(str->answer_len));
if(!str->answer)
fatal_exit("sldns_buffer_new failed: out of memory");
sldns_buffer_set_limit(str->answer, ntohs(str->answer_len));
}
/** print the answer rrs */
static void
print_answer_rrs(uint8_t* pkt, size_t pktlen)
{
char buf[65535];
char* str;
size_t str_len;
int i, qdcount, ancount;
uint8_t* data = pkt;
size_t data_len = pktlen;
int comprloop = 0;
if(data_len < LDNS_HEADER_SIZE)
return;
qdcount = LDNS_QDCOUNT(data);
ancount = LDNS_ANCOUNT(data);
data += LDNS_HEADER_SIZE;
data_len -= LDNS_HEADER_SIZE;
for(i=0; i<qdcount; i++) {
str = buf;
str_len = sizeof(buf);
(void)sldns_wire2str_rrquestion_scan(&data, &data_len,
&str, &str_len, pkt, pktlen, &comprloop);
}
for(i=0; i<ancount; i++) {
str = buf;
str_len = sizeof(buf);
(void)sldns_wire2str_rr_scan(&data, &data_len, &str, &str_len,
pkt, pktlen, &comprloop);
/* terminate string */
if(str_len == 0)
buf[sizeof(buf)-1] = 0;
else *str = 0;
printf("%s", buf);
}
}
/** short output of answer, short error or rcode or answer section RRs. */
static void
client_stream_print_short(struct doq_client_stream* str)
{
int rcode, ancount;
if(str->query_has_error) {
char* logs = client_stream_string(str);
printf("%s has error, there is no answer\n", logs);
free(logs);
return;
}
if(sldns_buffer_limit(str->answer) < LDNS_HEADER_SIZE) {
char* logs = client_stream_string(str);
printf("%s received short packet, smaller than header\n",
logs);
free(logs);
return;
}
rcode = LDNS_RCODE_WIRE(sldns_buffer_begin(str->answer));
if(rcode != 0) {
char* logs = client_stream_string(str);
char rc[16];
(void)sldns_wire2str_rcode_buf(rcode, rc, sizeof(rc));
printf("%s rcode %s\n", logs, rc);
free(logs);
return;
}
ancount = LDNS_ANCOUNT(sldns_buffer_begin(str->answer));
if(ancount == 0) {
char* logs = client_stream_string(str);
printf("%s nodata answer\n", logs);
free(logs);
return;
}
print_answer_rrs(sldns_buffer_begin(str->answer),
sldns_buffer_limit(str->answer));
}
/** print the stream output answer */
static void
client_stream_print_long(struct doq_client_data* data,
struct doq_client_stream* str)
{
char* s;
if(str->query_has_error) {
char* logs = client_stream_string(str);
printf("%s has error, there is no answer\n", logs);
free(logs);
return;
}
s = sldns_wire2str_pkt(sldns_buffer_begin(str->answer),
sldns_buffer_limit(str->answer));
printf("%s", (s?s:";sldns_wire2str_pkt failed\n"));
printf(";; SERVER: %s %d\n", data->svr, data->port);
free(s);
}
/** the stream has completed the data */
static void
client_stream_data_complete(struct doq_client_stream* str)
{
verbose(1, "received all answer content");
if(verbosity > 0) {
char* logs = client_stream_string(str);
char* s;
log_buf(1, "received answer", str->answer);
s = sldns_wire2str_pkt(sldns_buffer_begin(str->answer),
sldns_buffer_limit(str->answer));
if(!s) verbose(1, "could not sldns_wire2str_pkt");
else verbose(1, "query %s received:\n%s", logs, s);
free(s);
free(logs);
}
str->answer_is_complete = 1;
}
/** the stream has completed but with an error */
static void
client_stream_answer_error(struct doq_client_stream* str)
{
if(verbosity > 0) {
char* logs = client_stream_string(str);
if(str->answer)
verbose(1, "query %s has an error. received %d/%d bytes.",
logs, (int)sldns_buffer_position(str->answer),
(int)sldns_buffer_limit(str->answer));
else
verbose(1, "query %s has an error. received no data.",
logs);
free(logs);
}
str->query_has_error = 1;
}
/** receive data for a stream */
static void
client_stream_recv_data(struct doq_client_stream* str, const uint8_t* data,
size_t datalen)
{
int got_data = 0;
/* read the tcplength uint16_t at the start of the DNS message */
if(str->nread < 2) {
size_t to_move = datalen;
if(datalen > 2-str->nread)
to_move = 2-str->nread;
memmove(((uint8_t*)&str->answer_len)+str->nread, data,
to_move);
str->nread += to_move;
data += to_move;
datalen -= to_move;
if(str->nread == 2) {
/* we can allocate the data buffer */
client_stream_datalen_complete(str);
}
}
/* if we have data bytes */
if(datalen > 0) {
size_t to_write = datalen;
if(datalen > sldns_buffer_remaining(str->answer))
to_write = sldns_buffer_remaining(str->answer);
if(to_write > 0) {
sldns_buffer_write(str->answer, data, to_write);
str->nread += to_write;
data += to_write;
datalen -= to_write;
got_data = 1;
}
}
/* extra received bytes after end? */
if(datalen > 0) {
verbose(1, "extra bytes after end of DNS length");
if(verbosity > 0)
log_hex("extradata", (void*)data, datalen);
}
/* are we done with it? */
if(got_data && str->nread >= (size_t)(ntohs(str->answer_len))+2) {
client_stream_data_complete(str);
}
}
/** receive FIN from remote end on client stream, no more data to be
* received on the stream. */
static void
client_stream_recv_fin(struct doq_client_data* data,
struct doq_client_stream* str, int is_fin)
{
if(verbosity > 0) {
char* logs = client_stream_string(str);
if(is_fin)
verbose(1, "query %s: received FIN from remote", logs);
else
verbose(1, "query %s: stream reset from remote", logs);
free(logs);
}
if(str->write_is_done)
stream_list_move(str, data->query_list_receive,
data->query_list_stop);
else
stream_list_move(str, data->query_list_send,
data->query_list_stop);
if(!str->answer_is_complete) {
client_stream_answer_error(str);
}
str->query_is_done = 1;
if(data->quiet)
client_stream_print_short(str);
else client_stream_print_long(data, str);
if(data->query_list_send->first==NULL &&
data->query_list_receive->first==NULL)
disconnect(data);
}
/** fill a buffer with random data */
static void fill_rand(struct ub_randstate* rnd, uint8_t* buf, size_t len)
{
if(RAND_bytes(buf, len) != 1) {
size_t i;
for(i=0; i<len; i++)
buf[i] = ub_random(rnd)&0xff;
}
}
/** create the static secret */
static void generate_static_secret(struct doq_client_data* data, size_t len)
{
data->static_secret_data = malloc(len);
if(!data->static_secret_data)
fatal_exit("malloc failed: out of memory");
data->static_secret_size = len;
fill_rand(data->rnd, data->static_secret_data, len);
}
/** fill cid structure with random data */
static void cid_randfill(struct ngtcp2_cid* cid, size_t datalen,
struct ub_randstate* rnd)
{
uint8_t buf[32];
if(datalen > sizeof(buf))
datalen = sizeof(buf);
fill_rand(rnd, buf, datalen);
ngtcp2_cid_init(cid, buf, datalen);
}
/** send buf on the client stream */
static int
client_bidi_stream(struct doq_client_data* data, int64_t* ret_stream_id,
void* stream_user_data)
{
int64_t stream_id;
int rv;
/* open new bidirectional stream */
rv = ngtcp2_conn_open_bidi_stream(data->conn, &stream_id,
stream_user_data);
if(rv != 0) {
if(rv == NGTCP2_ERR_STREAM_ID_BLOCKED) {
/* no bidi stream count for this new stream */
return 0;
}
fatal_exit("could not ngtcp2_conn_open_bidi_stream: %s",
ngtcp2_strerror(rv));
}
*ret_stream_id = stream_id;
return 1;
}
/** See if we can start query streams, by creating bidirectional streams
* on the QUIC transport for them. */
static void
query_streams_start(struct doq_client_data* data)
{
while(data->query_list_start->first) {
struct doq_client_stream* str = data->query_list_start->first;
int64_t stream_id = 0;
if(!client_bidi_stream(data, &stream_id, str)) {
/* no more bidi streams allowed */
break;
}
if(verbosity > 0) {
char* logs = client_stream_string(str);
verbose(1, "query %s start on bidi stream id %lld",
logs, (long long int)stream_id);
free(logs);
}
/* setup the stream to start */
client_stream_start_setup(str, stream_id);
/* move the query entry to the send list to write it */
stream_list_move(str, data->query_list_start,
data->query_list_send);
}
}
/** the rand callback routine from ngtcp2 */
static void rand_cb(uint8_t* dest, size_t destlen,
const ngtcp2_rand_ctx* rand_ctx)
{
struct ub_randstate* rnd = (struct ub_randstate*)
rand_ctx->native_handle;
fill_rand(rnd, dest, destlen);
}
/** the get_new_connection_id callback routine from ngtcp2 */
static int get_new_connection_id_cb(struct ngtcp2_conn* ATTR_UNUSED(conn),
struct ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data)
{
struct doq_client_data* data = (struct doq_client_data*)user_data;
cid_randfill(cid, cidlen, data->rnd);
if(ngtcp2_crypto_generate_stateless_reset_token(token,
data->static_secret_data, data->static_secret_size, cid) != 0)
return NGTCP2_ERR_CALLBACK_FAILURE;
return 0;
}
/** handle that early data is rejected */
static void
early_data_is_rejected(struct doq_client_data* data)
{
int rv;
verbose(1, "early data was rejected by the server");
#ifdef HAVE_NGTCP2_CONN_TLS_EARLY_DATA_REJECTED
rv = ngtcp2_conn_tls_early_data_rejected(data->conn);
#else
rv = ngtcp2_conn_early_data_rejected(data->conn);
#endif
if(rv != 0) {
log_err("ngtcp2_conn_early_data_rejected failed: %s",
ngtcp2_strerror(rv));
return;
}
/* move the streams back to the start state */
while(data->query_list_send->first) {
struct doq_client_stream* str = data->query_list_send->first;
/* move it back to the start list */
stream_list_move(str, data->query_list_send,
data->query_list_start);
str->has_stream = 0;
/* remove stream id */
str->stream_id = 0;
/* initialise other members, in case they are altered,
* but unlikely, because early streams are rejected. */
str->nwrite = 0;
str->nread = 0;
str->answer_len = 0;
str->query_is_done = 0;
str->answer_is_complete = 0;
str->query_has_error = 0;
if(str->answer) {
sldns_buffer_free(str->answer);
str->answer = NULL;
}
}
}
/** the handshake completed callback from ngtcp2 */
static int
handshake_completed(ngtcp2_conn* ATTR_UNUSED(conn), void* user_data)
{
struct doq_client_data* data = (struct doq_client_data*)user_data;
verbose(1, "handshake_completed callback");
verbose(1, "ngtcp2_conn_get_max_data_left is %d",
(int)ngtcp2_conn_get_max_data_left(data->conn));
#ifdef HAVE_NGTCP2_CONN_GET_MAX_LOCAL_STREAMS_UNI
verbose(1, "ngtcp2_conn_get_max_local_streams_uni is %d",
(int)ngtcp2_conn_get_max_local_streams_uni(data->conn));
#endif
verbose(1, "ngtcp2_conn_get_streams_uni_left is %d",
(int)ngtcp2_conn_get_streams_uni_left(data->conn));
verbose(1, "ngtcp2_conn_get_streams_bidi_left is %d",
(int)ngtcp2_conn_get_streams_bidi_left(data->conn));
verbose(1, "negotiated cipher name is %s",
SSL_get_cipher_name(data->ssl));
if(verbosity > 0) {
const unsigned char* alpn = NULL;
unsigned int alpnlen = 0;
char alpnstr[128];
SSL_get0_alpn_selected(data->ssl, &alpn, &alpnlen);
if(alpnlen > sizeof(alpnstr)-1)
alpnlen = sizeof(alpnstr)-1;
memmove(alpnstr, alpn, alpnlen);
alpnstr[alpnlen]=0;
verbose(1, "negotiated ALPN is '%s'", alpnstr);
}
/* The SSL_get_early_data_status call works after the handshake
* completes. */
if(data->early_data_enabled) {
if(SSL_get_early_data_status(data->ssl) !=
SSL_EARLY_DATA_ACCEPTED) {
early_data_is_rejected(data);
} else {
verbose(1, "early data was accepted by the server");
}
}
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
if(data->transport_file) {
early_data_write_transport(data);
}
#endif
return 0;
}
/** the extend_max_local_streams_bidi callback from ngtcp2 */
static int
extend_max_local_streams_bidi(ngtcp2_conn* ATTR_UNUSED(conn),
uint64_t max_streams, void* user_data)
{
struct doq_client_data* data = (struct doq_client_data*)user_data;
verbose(1, "extend_max_local_streams_bidi callback, %d max_streams",
(int)max_streams);
verbose(1, "ngtcp2_conn_get_max_data_left is %d",
(int)ngtcp2_conn_get_max_data_left(data->conn));
#ifdef HAVE_NGTCP2_CONN_GET_MAX_LOCAL_STREAMS_UNI
verbose(1, "ngtcp2_conn_get_max_local_streams_uni is %d",
(int)ngtcp2_conn_get_max_local_streams_uni(data->conn));
#endif
verbose(1, "ngtcp2_conn_get_streams_uni_left is %d",
(int)ngtcp2_conn_get_streams_uni_left(data->conn));
verbose(1, "ngtcp2_conn_get_streams_bidi_left is %d",
(int)ngtcp2_conn_get_streams_bidi_left(data->conn));
query_streams_start(data);
return 0;
}
/** the recv_stream_data callback from ngtcp2 */
static int
recv_stream_data(ngtcp2_conn* ATTR_UNUSED(conn), uint32_t flags,
int64_t stream_id, uint64_t offset, const uint8_t* data,
size_t datalen, void* user_data, void* stream_user_data)
{
struct doq_client_data* doqdata = (struct doq_client_data*)user_data;
struct doq_client_stream* str = (struct doq_client_stream*)
stream_user_data;
verbose(1, "recv_stream_data stream %d offset %d datalen %d%s%s",
(int)stream_id, (int)offset, (int)datalen,
((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0?" FIN":""),
#ifdef NGTCP2_STREAM_DATA_FLAG_0RTT
((flags&NGTCP2_STREAM_DATA_FLAG_0RTT)!=0?" 0RTT":"")
#else
((flags&NGTCP2_STREAM_DATA_FLAG_EARLY)!=0?" EARLY":"")
#endif
);
if(verbosity > 0)
log_hex("data", (void*)data, datalen);
if(verbosity > 0) {
char* logs = client_stream_string(str);
verbose(1, "the stream_user_data is %s stream id %d, nread %d",
logs, (int)str->stream_id, (int)str->nread);
free(logs);
}
/* append the data, if there is data */
if(datalen > 0) {
client_stream_recv_data(str, data, datalen);
}
if((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0) {
client_stream_recv_fin(doqdata, str, 1);
}
ngtcp2_conn_extend_max_stream_offset(doqdata->conn, stream_id, datalen);
ngtcp2_conn_extend_max_offset(doqdata->conn, datalen);
return 0;
}
/** the stream reset callback from ngtcp2 */
static int
stream_reset(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id,
uint64_t final_size, uint64_t app_error_code, void* user_data,
void* stream_user_data)
{
struct doq_client_data* doqdata = (struct doq_client_data*)user_data;
struct doq_client_stream* str = (struct doq_client_stream*)
stream_user_data;
verbose(1, "stream reset for stream %d final size %d app error code %d",
(int)stream_id, (int)final_size, (int)app_error_code);
client_stream_recv_fin(doqdata, str, 0);
return 0;
}
/** copy sockaddr into ngtcp2 addr */
static void
copy_ngaddr(struct ngtcp2_addr* ngaddr, struct sockaddr_storage* addr,
socklen_t addrlen)
{
if(addr_is_ip6(addr, addrlen)) {
#if defined(NGTCP2_USE_GENERIC_SOCKADDR) || defined(NGTCP2_USE_GENERIC_IPV6_SOCKADDR)
struct sockaddr_in* i6 = (struct sockaddr_in6*)addr;
struct ngtcp2_sockaddr_in6 a6;
ngaddr->addr = calloc(1, sizeof(a6));
if(!ngaddr->addr) fatal_exit("calloc failed: out of memory");
ngaddr->addrlen = sizeof(a6);
memset(&a6, 0, sizeof(a6));
a6.sin6_family = i6->sin6_family;
a6.sin6_port = i6->sin6_port;
a6.sin6_flowinfo = i6->sin6_flowinfo;
memmove(&a6.sin6_addr, i6->sin6_addr, sizeof(a6.sin6_addr);
a6.sin6_scope_id = i6->sin6_scope_id;
memmove(ngaddr->addr, &a6, sizeof(a6));
#else
ngaddr->addr = (ngtcp2_sockaddr*)addr;
ngaddr->addrlen = addrlen;
#endif
} else {
#ifdef NGTCP2_USE_GENERIC_SOCKADDR
struct sockaddr_in* i4 = (struct sockaddr_in*)addr;
struct ngtcp2_sockaddr_in a4;
ngaddr->addr = calloc(1, sizeof(a4));
if(!ngaddr->addr) fatal_exit("calloc failed: out of memory");
ngaddr->addrlen = sizeof(a4);
memset(&a4, 0, sizeof(a4));
a4.sin_family = i4->sin_family;
a4.sin_port = i4->sin_port;
memmove(&a4.sin_addr, i4->sin_addr, sizeof(a4.sin_addr);
memmove(ngaddr->addr, &a4, sizeof(a4));
#else
ngaddr->addr = (ngtcp2_sockaddr*)addr;
ngaddr->addrlen = addrlen;
#endif
}
}
/** debug log printf for ngtcp2 connections */
static void log_printf_for_doq(void* ATTR_UNUSED(user_data),
const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "libngtcp2: ");
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
/** get a timestamp in nanoseconds */
static ngtcp2_tstamp get_timestamp_nanosec(void)
{
#ifdef CLOCK_REALTIME
struct timespec tp;
memset(&tp, 0, sizeof(tp));
#ifdef CLOCK_MONOTONIC
if(clock_gettime(CLOCK_MONOTONIC, &tp) == -1) {
#endif
if(clock_gettime(CLOCK_REALTIME, &tp) == -1) {
log_err("clock_gettime failed: %s", strerror(errno));
}
#ifdef CLOCK_MONOTONIC
}
#endif
return ((uint64_t)tp.tv_sec)*((uint64_t)1000000000) +
((uint64_t)tp.tv_nsec);
#else
struct timeval tv;
if(gettimeofday(&tv, NULL) < 0) {
log_err("gettimeofday failed: %s", strerror(errno));
}
return ((uint64_t)tv.tv_sec)*((uint64_t)1000000000) +
((uint64_t)tv.tv_usec)*((uint64_t)1000);
#endif /* CLOCK_REALTIME */
}
/** create ngtcp2 client connection and set up. */
static struct ngtcp2_conn* conn_client_setup(struct doq_client_data* data)
{
struct ngtcp2_conn* conn = NULL;
int rv;
struct ngtcp2_cid dcid, scid;
struct ngtcp2_path path;
uint32_t client_chosen_version = NGTCP2_PROTO_VER_V1;
struct ngtcp2_callbacks cbs;
struct ngtcp2_settings settings;
struct ngtcp2_transport_params params;
memset(&cbs, 0, sizeof(cbs));
memset(&settings, 0, sizeof(settings));
memset(&params, 0, sizeof(params));
memset(&dcid, 0, sizeof(dcid));
memset(&scid, 0, sizeof(scid));
memset(&path, 0, sizeof(path));
data->quic_version = client_chosen_version;
ngtcp2_settings_default(&settings);
if(str_is_ip6(data->svr)) {
#ifdef HAVE_STRUCT_NGTCP2_SETTINGS_MAX_TX_UDP_PAYLOAD_SIZE
settings.max_tx_udp_payload_size = 1232;
#else
settings.max_udp_payload_size = 1232;
#endif
}
settings.rand_ctx.native_handle = data->rnd;
if(verbosity > 0) {
/* make debug logs */
settings.log_printf = log_printf_for_doq;
}
settings.initial_ts = get_timestamp_nanosec();
ngtcp2_transport_params_default(&params);
params.initial_max_stream_data_bidi_local = 256*1024;
params.initial_max_stream_data_bidi_remote = 256*1024;
params.initial_max_stream_data_uni = 256*1024;
params.initial_max_data = 1024*1024;
params.initial_max_streams_bidi = 0;
params.initial_max_streams_uni = 100;
params.max_idle_timeout = 30*NGTCP2_SECONDS;
params.active_connection_id_limit = 7;
cid_randfill(&dcid, 16, data->rnd);
cid_randfill(&scid, 16, data->rnd);
cbs.client_initial = ngtcp2_crypto_client_initial_cb;
cbs.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
cbs.encrypt = ngtcp2_crypto_encrypt_cb;
cbs.decrypt = ngtcp2_crypto_decrypt_cb;
cbs.hp_mask = ngtcp2_crypto_hp_mask_cb;
cbs.recv_retry = ngtcp2_crypto_recv_retry_cb;
cbs.update_key = ngtcp2_crypto_update_key_cb;
cbs.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
cbs.delete_crypto_cipher_ctx =
ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
cbs.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
cbs.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
cbs.get_new_connection_id = get_new_connection_id_cb;
cbs.handshake_completed = handshake_completed;
cbs.extend_max_local_streams_bidi = extend_max_local_streams_bidi;
cbs.rand = rand_cb;
cbs.recv_stream_data = recv_stream_data;
cbs.stream_reset = stream_reset;
copy_ngaddr(&path.local, &data->local_addr, data->local_addr_len);
copy_ngaddr(&path.remote, &data->dest_addr, data->dest_addr_len);
rv = ngtcp2_conn_client_new(&conn, &dcid, &scid, &path,
client_chosen_version, &cbs, &settings, &params,
NULL, /* ngtcp2_mem allocator, use default */
data /* callback argument */);
if(!conn) fatal_exit("could not ngtcp2_conn_client_new: %s",
ngtcp2_strerror(rv));
data->cc_algo = settings.cc_algo;
return conn;
}
#ifndef HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS
/** write the transport file */
static void
transport_file_write(const char* file, struct ngtcp2_transport_params* params)
{
FILE* out;
out = fopen(file, "w");
if(!out) {
perror(file);
return;
}
fprintf(out, "initial_max_streams_bidi=%u\n",
(unsigned)params->initial_max_streams_bidi);
fprintf(out, "initial_max_streams_uni=%u\n",
(unsigned)params->initial_max_streams_uni);
fprintf(out, "initial_max_stream_data_bidi_local=%u\n",
(unsigned)params->initial_max_stream_data_bidi_local);
fprintf(out, "initial_max_stream_data_bidi_remote=%u\n",
(unsigned)params->initial_max_stream_data_bidi_remote);
fprintf(out, "initial_max_stream_data_uni=%u\n",
(unsigned)params->initial_max_stream_data_uni);
fprintf(out, "initial_max_data=%u\n",
(unsigned)params->initial_max_data);
fprintf(out, "active_connection_id_limit=%u\n",
(unsigned)params->active_connection_id_limit);
fprintf(out, "max_datagram_frame_size=%u\n",
(unsigned)params->max_datagram_frame_size);
if(ferror(out)) {
verbose(1, "There was an error writing %s: %s",
file, strerror(errno));
fclose(out);
return;
}
fclose(out);
}
#endif /* HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS */
/** fetch and write the transport file */
static void
early_data_write_transport(struct doq_client_data* data)
{
#ifdef HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS
FILE* out;
uint8_t buf[1024];
ngtcp2_ssize len = ngtcp2_conn_encode_0rtt_transport_params(data->conn,
buf, sizeof(buf));
if(len < 0) {
log_err("ngtcp2_conn_encode_0rtt_transport_params failed: %s",
ngtcp2_strerror(len));
return;
}
out = fopen(data->transport_file, "w");
if(!out) {
perror(data->transport_file);
return;
}
if(fwrite(buf, 1, len, out) != (size_t)len) {
log_err("fwrite %s failed: %s", data->transport_file,
strerror(errno));
}
if(ferror(out)) {
verbose(1, "There was an error writing %s: %s",
data->transport_file, strerror(errno));
}
fclose(out);
#else
struct ngtcp2_transport_params params;
memset(&params, 0, sizeof(params));
ngtcp2_conn_get_remote_transport_params(data->conn, &params);
transport_file_write(data->transport_file, &params);
#endif
}
#ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
/** applicatation rx key callback, this is where the rx key is set,
* and streams can be opened, like http3 unidirectional streams, like
* the http3 control and http3 qpack encode and decoder streams. */
static int
application_rx_key_cb(struct doq_client_data* data)
{
verbose(1, "application_rx_key_cb callback");
verbose(1, "ngtcp2_conn_get_max_data_left is %d",
(int)ngtcp2_conn_get_max_data_left(data->conn));
#ifdef HAVE_NGTCP2_CONN_GET_MAX_LOCAL_STREAMS_UNI
verbose(1, "ngtcp2_conn_get_max_local_streams_uni is %d",
(int)ngtcp2_conn_get_max_local_streams_uni(data->conn));
#endif
verbose(1, "ngtcp2_conn_get_streams_uni_left is %d",
(int)ngtcp2_conn_get_streams_uni_left(data->conn));
verbose(1, "ngtcp2_conn_get_streams_bidi_left is %d",
(int)ngtcp2_conn_get_streams_bidi_left(data->conn));
if(data->transport_file) {
early_data_write_transport(data);
}
return 1;
}
/** quic_method set_encryption_secrets function */
static int
set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
const uint8_t *read_secret, const uint8_t *write_secret,
size_t secret_len)
{
struct doq_client_data* data = get_app_data(ssl);
#ifdef HAVE_NGTCP2_ENCRYPTION_LEVEL
ngtcp2_encryption_level
#else
ngtcp2_crypto_level
#endif
level =
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_FROM_OSSL_ENCRYPTION_LEVEL
ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level);
#else
ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level);
#endif
if(read_secret) {
if(ngtcp2_crypto_derive_and_install_rx_key(data->conn, NULL,
NULL, NULL, level, read_secret, secret_len) != 0) {
log_err("ngtcp2_crypto_derive_and_install_rx_key failed");
return 0;
}
if(level == NGTCP2_CRYPTO_LEVEL_APPLICATION) {
if(!application_rx_key_cb(data))
return 0;
}
}
if(write_secret) {
if(ngtcp2_crypto_derive_and_install_tx_key(data->conn, NULL,
NULL, NULL, level, write_secret, secret_len) != 0) {
log_err("ngtcp2_crypto_derive_and_install_tx_key failed");
return 0;
}
}
return 1;
}
/** quic_method add_handshake_data function */
static int
add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
const uint8_t *data, size_t len)
{
struct doq_client_data* doqdata = get_app_data(ssl);
#ifdef HAVE_NGTCP2_ENCRYPTION_LEVEL
ngtcp2_encryption_level
#else
ngtcp2_crypto_level
#endif
level =
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_FROM_OSSL_ENCRYPTION_LEVEL
ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level);
#else
ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level);
#endif
int rv;
rv = ngtcp2_conn_submit_crypto_data(doqdata->conn, level, data, len);
if(rv != 0) {
log_err("ngtcp2_conn_submit_crypto_data failed: %s",
ngtcp2_strerror(rv));
ngtcp2_conn_set_tls_error(doqdata->conn, rv);
return 0;
}
return 1;
}
/** quic_method flush_flight function */
static int
flush_flight(SSL* ATTR_UNUSED(ssl))
{
return 1;
}
/** quic_method send_alert function */
static int
send_alert(SSL *ssl, enum ssl_encryption_level_t ATTR_UNUSED(level),
uint8_t alert)
{
struct doq_client_data* data = get_app_data(ssl);
data->tls_alert = alert;
return 1;
}
#endif /* HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT */
/** new session callback. We can write it to file for resumption later. */
static int
new_session_cb(SSL* ssl, SSL_SESSION* session)
{
struct doq_client_data* data = get_app_data(ssl);
BIO *f;
log_assert(data->session_file);
verbose(1, "new session cb: the ssl session max_early_data_size is %u",
(unsigned)SSL_SESSION_get_max_early_data(session));
f = BIO_new_file(data->session_file, "w");
if(!f) {
log_err("Could not open %s: %s", data->session_file,
strerror(errno));
return 0;
}
PEM_write_bio_SSL_SESSION(f, session);
BIO_free(f);
verbose(1, "written tls session to %s", data->session_file);
return 0;
}
/** setup the TLS context */
static SSL_CTX*
ctx_client_setup(void)
{
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
if(!ctx) {
log_crypto_err("Could not SSL_CTX_new");
exit(1);
}
SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
SSL_CTX_set_default_verify_paths(ctx);
#ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_CLIENT_CONTEXT
if(ngtcp2_crypto_quictls_configure_client_context(ctx) != 0) {
log_err("ngtcp2_crypto_quictls_configure_client_context failed");
exit(1);
}
#else
memset(&quic_method, 0, sizeof(quic_method));
quic_method.set_encryption_secrets = &set_encryption_secrets;
quic_method.add_handshake_data = &add_handshake_data;
quic_method.flush_flight = &flush_flight;
quic_method.send_alert = &send_alert;
SSL_CTX_set_quic_method(ctx, &quic_method);
#endif
return ctx;
}
/* setup the TLS object */
static SSL*
ssl_client_setup(struct doq_client_data* data)
{
SSL* ssl = SSL_new(data->ctx);
if(!ssl) {
log_crypto_err("Could not SSL_new");
exit(1);
}
set_app_data(ssl, data);
SSL_set_connect_state(ssl);
if(!SSL_set_fd(ssl, data->fd)) {
log_crypto_err("Could not SSL_set_fd");
exit(1);
}
if((data->quic_version & 0xff000000) == 0xff000000) {
SSL_set_quic_use_legacy_codepoint(ssl, 1);
} else {
SSL_set_quic_use_legacy_codepoint(ssl, 0);
}
SSL_set_alpn_protos(ssl, (const unsigned char *)"\x03""doq", 4);
/* send the SNI host name */
SSL_set_tlsext_host_name(ssl, "localhost");
return ssl;
}
/** get packet ecn information */
static uint32_t
msghdr_get_ecn(struct msghdr* msg, int family)
{
#ifndef S_SPLINT_S
struct cmsghdr* cmsg;
if(family == AF_INET6) {
for(cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if(cmsg->cmsg_level == IPPROTO_IPV6 &&
cmsg->cmsg_type == IPV6_TCLASS &&
cmsg->cmsg_len != 0) {
uint8_t* ecn = (uint8_t*)CMSG_DATA(cmsg);
return *ecn;
}
}
return 0;
}
for(cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if(cmsg->cmsg_level == IPPROTO_IP &&
cmsg->cmsg_type == IP_TOS &&
cmsg->cmsg_len != 0) {
uint8_t* ecn = (uint8_t*)CMSG_DATA(cmsg);
return *ecn;
}
}
return 0;
#endif /* S_SPLINT_S */
}
/** set the ecn on the transmission */
static void
set_ecn(int fd, int family, uint32_t ecn)
{
unsigned int val = ecn;
if(family == AF_INET6) {
if(setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val,
(socklen_t)sizeof(val)) == -1) {
log_err("setsockopt(.. IPV6_TCLASS ..): %s",
strerror(errno));
}
return;
}
if(setsockopt(fd, IPPROTO_IP, IP_TOS, &val,
(socklen_t)sizeof(val)) == -1) {
log_err("setsockopt(.. IP_TOS ..): %s",
strerror(errno));
}
}
/** send a packet */
static int
doq_client_send_pkt(struct doq_client_data* data, uint32_t ecn, uint8_t* buf,
size_t buf_len, int is_blocked_pkt, int* send_is_blocked)
{
struct msghdr msg;
struct iovec iov[1];
ssize_t ret;
iov[0].iov_base = buf;
iov[0].iov_len = buf_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void*)&data->dest_addr;
msg.msg_namelen = data->dest_addr_len;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
set_ecn(data->fd, data->dest_addr.ss_family, ecn);
for(;;) {
ret = sendmsg(data->fd, &msg, MSG_DONTWAIT);
if(ret == -1 && errno == EINTR)
continue;
break;
}
if(ret == -1) {
if(errno == EAGAIN) {
if(buf_len >
sldns_buffer_capacity(data->blocked_pkt))
return 0; /* Cannot store it, but the buffers
are equal length and large enough, so this
should not happen. */
data->have_blocked_pkt = 1;
if(send_is_blocked)
*send_is_blocked = 1;
/* If we already send the previously blocked packet,
* no need to copy it, otherwise store the packet for
* later. */
if(!is_blocked_pkt) {
data->blocked_pkt_pi.ecn = ecn;
sldns_buffer_clear(data->blocked_pkt);
sldns_buffer_write(data->blocked_pkt, buf,
buf_len);
sldns_buffer_flip(data->blocked_pkt);
}
return 0;
}
log_err("doq sendmsg: %s", strerror(errno));
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_set_application_error(&data->ccerr, -1, NULL, 0);
#else
ngtcp2_connection_close_error_set_application_error(&data->last_error, -1, NULL, 0);
#endif
return 0;
}
return 1;
}
/** change event write on fd to when we have data or when congested */
static void
event_change_write(struct doq_client_data* data, int do_write)
{
ub_event_del(data->ev);
if(do_write) {
ub_event_add_bits(data->ev, UB_EV_WRITE);
} else {
ub_event_del_bits(data->ev, UB_EV_WRITE);
}
if(ub_event_add(data->ev, NULL) != 0) {
fatal_exit("could not ub_event_add");
}
}
/** write the connection close, with possible error */
static void
write_conn_close(struct doq_client_data* data)
{
struct ngtcp2_path_storage ps;
struct ngtcp2_pkt_info pi;
ngtcp2_ssize ret;
if(!data->conn ||
#ifdef HAVE_NGTCP2_CONN_IN_CLOSING_PERIOD
ngtcp2_conn_in_closing_period(data->conn) ||
#else
ngtcp2_conn_is_in_closing_period(data->conn) ||
#endif
#ifdef HAVE_NGTCP2_CONN_IN_DRAINING_PERIOD
ngtcp2_conn_in_draining_period(data->conn)
#else
ngtcp2_conn_is_in_draining_period(data->conn)
#endif
)
return;
/* Drop blocked packet if there is one, the connection is being
* closed. And thus no further data traffic. */
data->have_blocked_pkt = 0;
if(
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
data->ccerr.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE
#else
data->last_error.type ==
NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE
#endif
) {
/* do not call ngtcp2_conn_write_connection_close on the
* connection because the ngtcp2_conn_handle_expiry call
* has returned NGTCP2_ERR_IDLE_CLOSE. But continue to close
* the connection. */
return;
}
verbose(1, "write connection close");
ngtcp2_path_storage_zero(&ps);
sldns_buffer_clear(data->pkt_buf);
ret = ngtcp2_conn_write_connection_close(
data->conn, &ps.path, &pi, sldns_buffer_begin(data->pkt_buf),
sldns_buffer_remaining(data->pkt_buf),
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
&data->ccerr
#else
&data->last_error
#endif
, get_timestamp_nanosec());
if(ret < 0) {
log_err("ngtcp2_conn_write_connection_close failed: %s",
ngtcp2_strerror(ret));
return;
}
verbose(1, "write connection close packet length %d", (int)ret);
if(ret == 0)
return;
doq_client_send_pkt(data, pi.ecn, sldns_buffer_begin(data->pkt_buf),
ret, 0, NULL);
}
/** disconnect we are done */
static void
disconnect(struct doq_client_data* data)
{
verbose(1, "disconnect");
write_conn_close(data);
ub_event_base_loopexit(data->base);
}
/** the expire timer callback */
void doq_client_timer_cb(int ATTR_UNUSED(fd),
short ATTR_UNUSED(bits), void* arg)
{
struct doq_client_data* data = (struct doq_client_data*)arg;
ngtcp2_tstamp now = get_timestamp_nanosec();
int rv;
verbose(1, "doq expire_timer");
data->expire_timer_added = 0;
rv = ngtcp2_conn_handle_expiry(data->conn, now);
if(rv != 0) {
log_err("ngtcp2_conn_handle_expiry failed: %s",
ngtcp2_strerror(rv));
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_set_liberr(&data->ccerr, rv, NULL, 0);
#else
ngtcp2_connection_close_error_set_transport_error_liberr(
&data->last_error, rv, NULL, 0);
#endif
disconnect(data);
return;
}
update_timer(data);
on_write(data);
}
/** update the timers */
static void
update_timer(struct doq_client_data* data)
{
ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(data->conn);
ngtcp2_tstamp now = get_timestamp_nanosec();
ngtcp2_tstamp t;
struct timeval tv;
if(expiry <= now) {
/* the timer has already expired, add with zero timeout */
t = 0;
} else {
t = expiry - now;
}
/* set the timer */
if(data->expire_timer_added) {
ub_timer_del(data->expire_timer);
data->expire_timer_added = 0;
}
memset(&tv, 0, sizeof(tv));
tv.tv_sec = t / NGTCP2_SECONDS;
tv.tv_usec = (t / NGTCP2_MICROSECONDS)%1000000;
verbose(1, "update_timer in %d.%6.6d secs", (int)tv.tv_sec,
(int)tv.tv_usec);
if(ub_timer_add(data->expire_timer, data->base,
&doq_client_timer_cb, data, &tv) != 0) {
log_err("timer_add failed: could not add expire timer");
return;
}
data->expire_timer_added = 1;
}
/** perform read operations on fd */
static void
on_read(struct doq_client_data* data)
{
struct sockaddr_storage addr;
struct iovec iov[1];
struct msghdr msg;
union {
struct cmsghdr hdr;
char buf[256];
} ancil;
int i;
ssize_t rcv;
ngtcp2_pkt_info pi;
int rv;
struct ngtcp2_path path;
for(i=0; i<10; i++) {
msg.msg_name = &addr;
msg.msg_namelen = (socklen_t)sizeof(addr);
iov[0].iov_base = sldns_buffer_begin(data->pkt_buf);
iov[0].iov_len = sldns_buffer_remaining(data->pkt_buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = ancil.buf;
#ifndef S_SPLINT_S
msg.msg_controllen = sizeof(ancil.buf);
#endif /* S_SPLINT_S */
msg.msg_flags = 0;
rcv = recvmsg(data->fd, &msg, MSG_DONTWAIT);
if(rcv == -1) {
if(errno == EINTR || errno == EAGAIN)
break;
log_err_addr("doq recvmsg", strerror(errno),
&data->dest_addr, sizeof(data->dest_addr_len));
break;
}
pi.ecn = msghdr_get_ecn(&msg, addr.ss_family);
verbose(1, "recvmsg %d ecn=0x%x", (int)rcv, (int)pi.ecn);
memset(&path, 0, sizeof(path));
path.local.addr = (void*)&data->local_addr;
path.local.addrlen = data->local_addr_len;
path.remote.addr = (void*)msg.msg_name;
path.remote.addrlen = msg.msg_namelen;
rv = ngtcp2_conn_read_pkt(data->conn, &path, &pi,
iov[0].iov_base, rcv, get_timestamp_nanosec());
if(rv != 0) {
log_err("ngtcp2_conn_read_pkt failed: %s",
ngtcp2_strerror(rv));
if(
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
data->ccerr.error_code == 0
#else
data->last_error.error_code == 0
#endif
) {
if(rv == NGTCP2_ERR_CRYPTO) {
/* in picotls the tls alert may need
* to be copied, but this is with
* openssl. And we have the value
* data.tls_alert. */
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_set_tls_alert(
&data->ccerr, data->tls_alert,
NULL, 0);
#else
ngtcp2_connection_close_error_set_transport_error_tls_alert(
&data->last_error,
data->tls_alert, NULL, 0);
#endif
} else {
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_set_liberr(&data->ccerr,
rv, NULL, 0);
#else
ngtcp2_connection_close_error_set_transport_error_liberr(
&data->last_error, rv, NULL,
0);
#endif
}
}
disconnect(data);
return;
}
}
update_timer(data);
}
/** the write of this query has completed, it has spooled to packets,
* set it to have the write done and move it to the list of receive streams. */
static void
query_write_is_done(struct doq_client_data* data,
struct doq_client_stream* str)
{
if(verbosity > 0) {
char* logs = client_stream_string(str);
verbose(1, "query %s write is done", logs);
free(logs);
}
str->write_is_done = 1;
stream_list_move(str, data->query_list_send, data->query_list_receive);
}
/** write the data streams, if possible */
static int
write_streams(struct doq_client_data* data)
{
ngtcp2_path_storage ps;
ngtcp2_tstamp ts = get_timestamp_nanosec();
struct doq_client_stream* str, *next;
uint32_t flags;
/* number of bytes that can be sent without packet pacing */
size_t send_quantum = ngtcp2_conn_get_send_quantum(data->conn);
/* Overhead is the stream overhead of adding a header onto the data,
* this make sure the number of bytes to send in data bytes plus
* the overhead overshoots the target quantum by a smaller margin,
* and then it stops sending more bytes. With zero it would overshoot
* more, an accurate number would not overshoot. It is based on the
* stream frame header size. */
size_t accumulated_send = 0, overhead_stream = 24, overhead_pkt = 60,
max_packet_size = 1200;
size_t num_packets = 0, max_packets = 65535;
ngtcp2_path_storage_zero(&ps);
str = data->query_list_send->first;
if(data->cc_algo != NGTCP2_CC_ALGO_BBR
#ifdef NGTCP2_CC_ALGO_BBR_V2
&& data->cc_algo != NGTCP2_CC_ALGO_BBR_V2
#endif
#ifdef NGTCP2_CC_ALGO_BBR2
&& data->cc_algo != NGTCP2_CC_ALGO_BBR2
#endif
) {
/* If we do not have a packet pacing congestion control
* algorithm, limit the number of packets. */
max_packets = 10;
}
/* loop like this, because at the start, the send list is empty,
* and we want to send handshake packets. But when there is a
* send_list, loop through that. */
for(;;) {
int64_t stream_id;
ngtcp2_pkt_info pi;
ngtcp2_vec datav[2];
size_t datav_count = 0;
int fin;
ngtcp2_ssize ret;
ngtcp2_ssize ndatalen = 0;
int send_is_blocked = 0;
if(str) {
/* pick up next in case this one is deleted */
next = str->next;
if(verbosity > 0) {
char* logs = client_stream_string(str);
verbose(1, "query %s write stream", logs);
free(logs);
}
stream_id = str->stream_id;
fin = 1;
if(str->nwrite < 2) {
str->data_tcplen = htons(str->data_len);
datav[0].base = ((uint8_t*)&str->data_tcplen)+str->nwrite;
datav[0].len = 2-str->nwrite;
datav[1].base = str->data;
datav[1].len = str->data_len;
datav_count = 2;
} else {
datav[0].base = str->data + (str->nwrite-2);
datav[0].len = str->data_len - (str->nwrite-2);
datav_count = 1;
}
} else {
next = NULL;
verbose(1, "write stream -1.");
stream_id = -1;
fin = 0;
datav[0].base = NULL;
datav[0].len = 0;
datav_count = 1;
}
/* Does the first data entry fit into the send quantum? */
/* Check if the data size sent, with a max of one full packet,
* with added stream header and packet header is allowed
* within the send quantum number of bytes. If not, it does
* not fit, and wait. */
if(accumulated_send == 0 && ((datav_count == 1 &&
(datav[0].len>max_packet_size?max_packet_size:
datav[0].len)+overhead_stream+overhead_pkt >
send_quantum) ||
(datav_count == 2 &&
(datav[0].len+datav[1].len>max_packet_size?
max_packet_size:datav[0].len+datav[1].len)
+overhead_stream+overhead_pkt > send_quantum))) {
/* congestion limited */
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
event_change_write(data, 0);
/* update the timer to wait until it is possible to
* write again */
update_timer(data);
return 0;
}
flags = 0;
if(str && str->next != NULL) {
/* Coalesce more data from more streams into this
* packet, if possible */
/* There is more than one data entry in this send
* quantum, does the next one fit in the quantum? */
size_t this_send, possible_next_send;
if(datav_count == 1)
this_send = datav[0].len;
else this_send = datav[0].len + datav[1].len;
if(this_send > max_packet_size)
this_send = max_packet_size;
if(str->next->nwrite < 2)
possible_next_send = (2-str->next->nwrite) +
str->next->data_len;
else possible_next_send = str->next->data_len -
(str->next->nwrite - 2);
if(possible_next_send > max_packet_size)
possible_next_send = max_packet_size;
/* Check if the data lengths that writev returned
* with stream headers added up so far, in
* accumulated_send, with added the data length
* of this send, with a max of one full packet, and
* the data length of the next possible send, with
* a max of one full packet, with a stream header for
* this_send and a stream header for the next possible
* send and a packet header, fit in the send quantum
* number of bytes. If so, ask to add more content
* to the packet with the more flag. */
if(accumulated_send + this_send + possible_next_send
+2*overhead_stream+ overhead_pkt < send_quantum)
flags |= NGTCP2_WRITE_STREAM_FLAG_MORE;
}
if(fin) {
/* This is the final part of data for this stream */
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
}
sldns_buffer_clear(data->pkt_buf);
ret = ngtcp2_conn_writev_stream(data->conn, &ps.path, &pi,
sldns_buffer_begin(data->pkt_buf),
sldns_buffer_remaining(data->pkt_buf), &ndatalen,
flags, stream_id, datav, datav_count, ts);
if(ret < 0) {
if(ret == NGTCP2_ERR_WRITE_MORE) {
if(str) {
str->nwrite += ndatalen;
if(str->nwrite >= str->data_len+2)
query_write_is_done(data, str);
str = next;
accumulated_send += ndatalen + overhead_stream;
continue;
}
}
log_err("ngtcp2_conn_writev_stream failed: %s",
ngtcp2_strerror(ret));
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_set_liberr(&data->ccerr, ret, NULL, 0);
#else
ngtcp2_connection_close_error_set_transport_error_liberr(
&data->last_error, ret, NULL, 0);
#endif
disconnect(data);
return 0;
}
verbose(1, "writev_stream pkt size %d ndatawritten %d",
(int)ret, (int)ndatalen);
if(ndatalen >= 0 && str) {
/* add the new write offset */
str->nwrite += ndatalen;
if(str->nwrite >= str->data_len+2)
query_write_is_done(data, str);
}
if(ret == 0) {
/* congestion limited */
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
event_change_write(data, 0);
/* update the timer to wait until it is possible to
* write again */
update_timer(data);
return 0;
}
if(!doq_client_send_pkt(data, pi.ecn,
sldns_buffer_begin(data->pkt_buf), ret, 0,
&send_is_blocked)) {
if(send_is_blocked) {
/* Blocked packet, wait until it is possible
* to write again and also set a timer. */
event_change_write(data, 1);
update_timer(data);
return 0;
}
/* Packet could not be sent. Like lost and timeout. */
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
event_change_write(data, 0);
update_timer(data);
return 0;
}
/* continue */
if((size_t)ret >= send_quantum)
break;
send_quantum -= ret;
accumulated_send = 0;
str = next;
if(str == NULL)
break;
if(++num_packets == max_packets)
break;
}
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
event_change_write(data, 1);
return 1;
}
/** send the blocked packet now that the stream is writable again. */
static int
send_blocked_pkt(struct doq_client_data* data)
{
ngtcp2_tstamp ts = get_timestamp_nanosec();
int send_is_blocked = 0;
if(!doq_client_send_pkt(data, data->blocked_pkt_pi.ecn,
sldns_buffer_begin(data->pkt_buf),
sldns_buffer_limit(data->pkt_buf), 1, &send_is_blocked)) {
if(send_is_blocked) {
/* Send was blocked, again. Wait, again to retry. */
event_change_write(data, 1);
/* make sure the timer is set while waiting */
update_timer(data);
return 0;
}
/* The packed could not be sent. Like it was lost, timeout. */
data->have_blocked_pkt = 0;
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
event_change_write(data, 0);
update_timer(data);
return 0;
}
/* The blocked packet has been sent, the holding buffer can be
* cleared. */
data->have_blocked_pkt = 0;
ngtcp2_conn_update_pkt_tx_time(data->conn, ts);
return 1;
}
/** perform write operations, if any, on fd */
static void
on_write(struct doq_client_data* data)
{
if(data->have_blocked_pkt) {
if(!send_blocked_pkt(data))
return;
}
if(
#ifdef HAVE_NGTCP2_CONN_IN_CLOSING_PERIOD
ngtcp2_conn_in_closing_period(data->conn)
#else
ngtcp2_conn_is_in_closing_period(data->conn)
#endif
)
return;
if(!write_streams(data))
return;
update_timer(data);
}
/** callback for main listening file descriptor */
void
doq_client_event_cb(int ATTR_UNUSED(fd), short bits, void* arg)
{
struct doq_client_data* data = (struct doq_client_data*)arg;
verbose(1, "doq_client_event_cb %s%s%s",
((bits&UB_EV_READ)!=0?"EV_READ":""),
((bits&(UB_EV_READ|UB_EV_WRITE))==(UB_EV_READ|UB_EV_WRITE)?
" ":""),
((bits&UB_EV_WRITE)!=0?"EV_WRITE":""));
if((bits&UB_EV_READ)) {
on_read(data);
}
/* Perform the write operation anyway. The read operation may
* have produced data, or there is content waiting and it is possible
* to write that. */
on_write(data);
}
/** read the TLS session from file */
static int
early_data_setup_session(struct doq_client_data* data)
{
SSL_SESSION* session;
BIO* f = BIO_new_file(data->session_file, "r");
if(f == NULL) {
if(errno == ENOENT) {
verbose(1, "session file %s does not exist",
data->session_file);
return 0;
}
log_err("Could not read %s: %s", data->session_file,
strerror(errno));
return 0;
}
session = PEM_read_bio_SSL_SESSION(f, NULL, 0, NULL);
if(session == NULL) {
log_crypto_err("Could not read session file with PEM_read_bio_SSL_SESSION");
BIO_free(f);
return 0;
}
BIO_free(f);
if(!SSL_set_session(data->ssl, session)) {
log_crypto_err("Could not SSL_set_session");
SSL_SESSION_free(session);
return 0;
}
if(SSL_SESSION_get_max_early_data(session) == 0) {
log_err("TLS session early data is 0");
SSL_SESSION_free(session);
return 0;
}
SSL_set_quic_early_data_enabled(data->ssl, 1);
SSL_SESSION_free(session);
return 1;
}
#ifndef HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS
/** parse one line from the transport file */
static int
transport_parse_line(struct ngtcp2_transport_params* params, char* line)
{
if(strncmp(line, "initial_max_streams_bidi=", 25) == 0) {
params->initial_max_streams_bidi = atoi(line+25);
return 1;
}
if(strncmp(line, "initial_max_streams_uni=", 24) == 0) {
params->initial_max_streams_uni = atoi(line+24);
return 1;
}
if(strncmp(line, "initial_max_stream_data_bidi_local=", 35) == 0) {
params->initial_max_stream_data_bidi_local = atoi(line+35);
return 1;
}
if(strncmp(line, "initial_max_stream_data_bidi_remote=", 36) == 0) {
params->initial_max_stream_data_bidi_remote = atoi(line+36);
return 1;
}
if(strncmp(line, "initial_max_stream_data_uni=", 28) == 0) {
params->initial_max_stream_data_uni = atoi(line+28);
return 1;
}
if(strncmp(line, "initial_max_data=", 17) == 0) {
params->initial_max_data = atoi(line+17);
return 1;
}
if(strncmp(line, "active_connection_id_limit=", 27) == 0) {
params->active_connection_id_limit = atoi(line+27);
return 1;
}
if(strncmp(line, "max_datagram_frame_size=", 24) == 0) {
params->max_datagram_frame_size = atoi(line+24);
return 1;
}
return 0;
}
#endif /* HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS */
/** setup the early data transport file and read it */
static int
early_data_setup_transport(struct doq_client_data* data)
{
#ifdef HAVE_NGTCP2_CONN_ENCODE_0RTT_TRANSPORT_PARAMS
FILE* in;
uint8_t buf[1024];
size_t len;
int rv;
in = fopen(data->transport_file, "r");
if(!in) {
if(errno == ENOENT) {
verbose(1, "transport file %s does not exist",
data->transport_file);
return 0;
}
perror(data->transport_file);
return 0;
}
len = fread(buf, 1, sizeof(buf), in);
if(ferror(in)) {
log_err("%s: read failed: %s", data->transport_file,
strerror(errno));
fclose(in);
return 0;
}
fclose(in);
rv = ngtcp2_conn_decode_and_set_0rtt_transport_params(data->conn,
buf, len);
if(rv != 0) {
log_err("ngtcp2_conn_decode_and_set_0rtt_transport_params failed: %s",
ngtcp2_strerror(rv));
return 0;
}
return 1;
#else
FILE* in;
char buf[1024];
struct ngtcp2_transport_params params;
memset(&params, 0, sizeof(params));
in = fopen(data->transport_file, "r");
if(!in) {
if(errno == ENOENT) {
verbose(1, "transport file %s does not exist",
data->transport_file);
return 0;
}
perror(data->transport_file);
return 0;
}
while(!feof(in)) {
if(!fgets(buf, sizeof(buf), in)) {
log_err("%s: read failed: %s", data->transport_file,
strerror(errno));
fclose(in);
return 0;
}
if(!transport_parse_line(&params, buf)) {
log_err("%s: could not parse line '%s'",
data->transport_file, buf);
fclose(in);
return 0;
}
}
fclose(in);
ngtcp2_conn_set_early_remote_transport_params(data->conn, &params);
#endif
return 1;
}
/** setup for early data, read the transport file and session file */
static void
early_data_setup(struct doq_client_data* data)
{
if(!early_data_setup_session(data)) {
verbose(1, "TLS session resumption failed, early data is disabled");
data->early_data_enabled = 0;
return;
}
if(!early_data_setup_transport(data)) {
verbose(1, "Transport parameters set failed, early data is disabled");
data->early_data_enabled = 0;
return;
}
}
/** start the early data transmission */
static void
early_data_start(struct doq_client_data* data)
{
query_streams_start(data);
on_write(data);
}
/** create doq_client_data */
static struct doq_client_data*
create_doq_client_data(const char* svr, int port, struct ub_event_base* base,
const char* transport_file, const char* session_file, int quiet)
{
struct doq_client_data* data;
data = calloc(1, sizeof(*data));
if(!data) fatal_exit("calloc failed: out of memory");
data->base = base;
data->rnd = ub_initstate(NULL);
if(!data->rnd) fatal_exit("ub_initstate failed: out of memory");
data->svr = svr;
get_dest_addr(data, svr, port);
data->port = port;
data->quiet = quiet;
data->pkt_buf = sldns_buffer_new(65552);
if(!data->pkt_buf)
fatal_exit("sldns_buffer_new failed: out of memory");
data->blocked_pkt = sldns_buffer_new(65552);
if(!data->blocked_pkt)
fatal_exit("sldns_buffer_new failed: out of memory");
data->fd = open_svr_udp(data);
get_local_addr(data);
data->conn = conn_client_setup(data);
#ifdef HAVE_NGTCP2_CCERR_DEFAULT
ngtcp2_ccerr_default(&data->ccerr);
#else
ngtcp2_connection_close_error_default(&data->last_error);
#endif
data->transport_file = transport_file;
data->session_file = session_file;
if(data->transport_file && data->session_file)
data->early_data_enabled = 1;
generate_static_secret(data, 32);
data->ctx = ctx_client_setup();
if(data->session_file) {
SSL_CTX_set_session_cache_mode(data->ctx,
SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL_STORE);
SSL_CTX_sess_set_new_cb(data->ctx, new_session_cb);
}
data->ssl = ssl_client_setup(data);
ngtcp2_conn_set_tls_native_handle(data->conn, data->ssl);
if(data->early_data_enabled)
early_data_setup(data);
data->ev = ub_event_new(base, data->fd, UB_EV_READ | UB_EV_WRITE |
UB_EV_PERSIST, doq_client_event_cb, data);
if(!data->ev) {
fatal_exit("could not ub_event_new");
}
if(ub_event_add(data->ev, NULL) != 0) {
fatal_exit("could not ub_event_add");
}
data->expire_timer = ub_event_new(data->base, -1,
UB_EV_TIMEOUT, &doq_client_timer_cb, data);
if(!data->expire_timer)
fatal_exit("could not ub_event_new");
data->query_list_start = stream_list_create();
data->query_list_send = stream_list_create();
data->query_list_receive = stream_list_create();
data->query_list_stop = stream_list_create();
return data;
}
/** delete doq_client_data */
static void
delete_doq_client_data(struct doq_client_data* data)
{
if(!data)
return;
#if defined(NGTCP2_USE_GENERIC_SOCKADDR) || defined(NGTCP2_USE_GENERIC_IPV6_SOCKADDR)
if(data->conn && data->dest_addr_len != 0) {
if(addr_is_ip6(&data->dest_addr, data->dest_addr_len)) {
# if defined(NGTCP2_USE_GENERIC_SOCKADDR) || defined(NGTCP2_USE_GENERIC_IPV6_SOCKADDR)
const struct ngtcp2_path* path6 = ngtcp2_conn_get_path(data->conn);
free(path6->local.addr);
free(path6->remote.addr);
# endif
} else {
# if defined(NGTCP2_USE_GENERIC_SOCKADDR)
const struct ngtcp2_path* path = ngtcp2_conn_get_path(data->conn);
free(path->local.addr);
free(path->remote.addr);
# endif
}
}
#endif
ngtcp2_conn_del(data->conn);
SSL_free(data->ssl);
sldns_buffer_free(data->pkt_buf);
sldns_buffer_free(data->blocked_pkt);
if(data->fd != -1)
sock_close(data->fd);
SSL_CTX_free(data->ctx);
stream_list_free(data->query_list_start);
stream_list_free(data->query_list_send);
stream_list_free(data->query_list_receive);
stream_list_free(data->query_list_stop);
ub_randfree(data->rnd);
if(data->ev) {
ub_event_del(data->ev);
ub_event_free(data->ev);
}
if(data->expire_timer_added)
ub_timer_del(data->expire_timer);
ub_event_free(data->expire_timer);
free(data->static_secret_data);
free(data);
}
/** create the event base that registers events and timers */
static struct ub_event_base*
create_event_base(time_t* secs, struct timeval* now)
{
struct ub_event_base* base;
const char *evnm="event", *evsys="", *evmethod="";
memset(now, 0, sizeof(*now));
base = ub_default_event_base(1, secs, now);
if(!base) fatal_exit("could not create ub_event base");
ub_get_event_sys(base, &evnm, &evsys, &evmethod);
if(verbosity) log_info("%s %s uses %s method", evnm, evsys, evmethod);
return base;
}
/** enter a query into the query list */
static void
client_enter_query_buf(struct doq_client_data* data, struct sldns_buffer* buf)
{
struct doq_client_stream* str;
str = client_stream_create(buf);
if(!str)
fatal_exit("client_stream_create failed: out of memory");
stream_list_append(data->query_list_start, str);
}
/** enter the queries into the query list */
static void
client_enter_queries(struct doq_client_data* data, char** qs, int count)
{
int i;
for(i=0; i<count; i+=3) {
struct sldns_buffer* buf = NULL;
buf = make_query(qs[i], qs[i+1], qs[i+2]);
if(verbosity > 0) {
char* str;
log_buf(1, "send query", buf);
str = sldns_wire2str_pkt(sldns_buffer_begin(buf),
sldns_buffer_limit(buf));
if(!str) verbose(1, "could not sldns_wire2str_pkt");
else verbose(1, "send query:\n%s", str);
free(str);
}
client_enter_query_buf(data, buf);
sldns_buffer_free(buf);
}
}
/** run the dohclient queries */
static void run(const char* svr, int port, char** qs, int count,
const char* transport_file, const char* session_file, int quiet)
{
time_t secs = 0;
struct timeval now;
struct ub_event_base* base;
struct doq_client_data* data;
/* setup */
base = create_event_base(&secs, &now);
data = create_doq_client_data(svr, port, base, transport_file,
session_file, quiet);
client_enter_queries(data, qs, count);
if(data->early_data_enabled)
early_data_start(data);
/* run the queries */
ub_event_base_dispatch(base);
/* cleanup */
delete_doq_client_data(data);
ub_event_base_free(base);
}
#endif /* HAVE_NGTCP2 */
#ifdef HAVE_NGTCP2
/** 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;
int main(int ATTR_UNUSED(argc), char** ATTR_UNUSED(argv))
{
int c;
int port = UNBOUND_DNS_OVER_QUIC_PORT, quiet = 0;
const char* svr = "127.0.0.1", *transport_file = NULL,
*session_file = NULL;
#ifdef USE_WINSOCK
WSADATA wsa_data;
if(WSAStartup(MAKEWORD(2,2), &wsa_data) != 0) {
printf("WSAStartup failed\n");
return 1;
}
#endif
checklock_set_output_name("ublocktrace-doqclient");
checklock_start();
log_init(0, 0, 0);
log_ident_set("doqclient");
while((c=getopt(argc, argv, "hp:qs:vx:y:")) != -1) {
switch(c) {
case 'p':
if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) {
printf("error parsing port, "
"number expected: %s\n", optarg);
return 1;
}
port = atoi(optarg);
break;
case 'q':
quiet++;
break;
case 's':
svr = optarg;
break;
case 'v':
verbosity++;
break;
case 'x':
transport_file = optarg;
break;
case 'y':
session_file = optarg;
break;
case 'h':
case '?':
default:
usage(argv);
}
}
argc -= optind;
argv += optind;
if(argc%3!=0) {
printf("Invalid input. Specify qname, qtype, and qclass.\n");
return 1;
}
if(port == 53) {
printf("Error: port number 53 not for DNS over QUIC. Port number 53 is not allowed to be used with DNS over QUIC. It is used for DNS datagrams.\n");
return 1;
}
run(svr, port, argv, argc, transport_file, session_file, quiet);
checklock_stop();
#ifdef USE_WINSOCK
WSACleanup();
#endif
return 0;
}
#else /* HAVE_NGTCP2 */
int main(int ATTR_UNUSED(argc), char** ATTR_UNUSED(argv))
{
printf("Compiled without ngtcp2 for QUIC, cannot run doqclient.\n");
return 1;
}
#endif /* HAVE_NGTCP2 */
/***--- 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"
void worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube),
uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len),
int ATTR_UNUSED(error), void* ATTR_UNUSED(arg))
{
log_assert(0);
}
int worker_handle_request(struct comm_point* ATTR_UNUSED(c),
void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
struct comm_reply* ATTR_UNUSED(repinfo))
{
log_assert(0);
return 0;
}
int worker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
struct comm_reply* ATTR_UNUSED(reply_info))
{
log_assert(0);
return 0;
}
int remote_accept_callback(struct comm_point* ATTR_UNUSED(c),
void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
struct comm_reply* ATTR_UNUSED(repinfo))
{
log_assert(0);
return 0;
}
int remote_control_callback(struct comm_point* ATTR_UNUSED(c),
void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
struct comm_reply* ATTR_UNUSED(repinfo))
{
log_assert(0);
return 0;
}
void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg))
{
log_assert(0);
}
struct outbound_entry* worker_send_query(
struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags),
int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec),
int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit),
struct sockaddr_storage* ATTR_UNUSED(addr),
socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone),
size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream),
int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name),
struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited))
{
log_assert(0);
return 0;
}
#ifdef UB_ON_WINDOWS
void
worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void*
ATTR_UNUSED(arg)) {
log_assert(0);
}
void
wsvc_cron_cb(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
#endif /* UB_ON_WINDOWS */
void
worker_alloc_cleanup(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
struct outbound_entry* libworker_send_query(
struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags),
int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec),
int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit),
struct sockaddr_storage* ATTR_UNUSED(addr),
socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone),
size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream),
int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name),
struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited))
{
log_assert(0);
return 0;
}
int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
struct comm_reply* ATTR_UNUSED(reply_info))
{
log_assert(0);
return 0;
}
void libworker_handle_control_cmd(struct tube* ATTR_UNUSED(tube),
uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len),
int ATTR_UNUSED(error), void* ATTR_UNUSED(arg))
{
log_assert(0);
}
void libworker_fg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
struct sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited))
{
log_assert(0);
}
void libworker_bg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
struct sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited))
{
log_assert(0);
}
void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
struct sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited))
{
log_assert(0);
}
int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
{
log_assert(0);
return 0;
}
void worker_stat_timer_cb(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
void worker_probe_timer_cb(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
void worker_start_accept(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
void worker_stop_accept(void* ATTR_UNUSED(arg))
{
log_assert(0);
}
/** 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;
};
int order_lock_cmp(const void* e1, const void* e2)
{
const struct order_id* o1 = e1;
const struct order_id* o2 = e2;
if(o1->thr < o2->thr) return -1;
if(o1->thr > o2->thr) return 1;
if(o1->instance < o2->instance) return -1;
if(o1->instance > o2->instance) return 1;
return 0;
}
int
codeline_cmp(const void* a, const void* b)
{
return strcmp(a, b);
}
int replay_var_compare(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
{
log_assert(0);
return 0;
}
void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg))
{
log_assert(0);
}