summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2022-01-27 20:23:34 +0100
committerGitHub <noreply@github.com>2022-01-27 20:23:34 +0100
commit33db66aa36d66144e5a18a62807ca60c1ca3fb6b (patch)
tree2ed81c915db85fc99df34f0b4cfd04a69467ca43
parentMerge pull request #22262 from DaanDeMeyer/journal-fixes (diff)
parentresolved: Test for DnsStream (plain TCP DNS and DoT) (diff)
downloadsystemd-33db66aa36d66144e5a18a62807ca60c1ca3fb6b.tar.xz
systemd-33db66aa36d66144e5a18a62807ca60c1ca3fb6b.zip
Merge pull request #22132 from joanbm/main
resolved: Fix DoT timeout on multiple answer records (for CloudFlare, Google, etc. DoT servers)
-rw-r--r--src/resolve/meson.build11
-rw-r--r--src/resolve/resolved-dns-stream.c45
-rw-r--r--src/resolve/resolved-dnstls-gnutls.c8
-rw-r--r--src/resolve/resolved-dnstls-openssl.c8
-rw-r--r--src/resolve/resolved-dnstls.h2
-rw-r--r--src/resolve/test-resolved-stream.c342
-rw-r--r--test/test-resolve/selfsigned.cert32
-rw-r--r--test/test-resolve/selfsigned.key52
8 files changed, 497 insertions, 3 deletions
diff --git a/src/resolve/meson.build b/src/resolve/meson.build
index eeaea3411f..24f94f8c0e 100644
--- a/src/resolve/meson.build
+++ b/src/resolve/meson.build
@@ -70,7 +70,6 @@ systemd_resolved_sources = files('''
resolved-socket-graveyard.h
resolved-varlink.c
resolved-varlink.h
- resolved.c
'''.split())
resolvectl_sources = files('''
@@ -200,6 +199,14 @@ tests += [
[lib_openssl_or_gcrypt,
libm]],
+ [files('test-resolved-stream.c')
+ + basic_dns_sources + systemd_resolved_sources,
+ [libshared],
+ [lib_openssl_or_gcrypt,
+ libm]
+ + systemd_resolved_dependencies,
+ resolve_includes],
+
[files('test-dnssec.c'),
[libsystemd_resolve_core,
libshared],
@@ -229,3 +236,5 @@ fuzzers += [
[lib_openssl_or_gcrypt,
libm]],
]
+
+systemd_resolved_sources += files('resolved.c')
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index f48e2a8029..51ffa6b4b0 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "io-util.h"
+#include "macro.h"
#include "missing_network.h"
#include "resolved-dns-stream.h"
#include "resolved-manager.h"
@@ -280,13 +281,15 @@ static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
return dns_stream_complete(s, ETIMEDOUT);
}
-static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
- _cleanup_(dns_stream_unrefp) DnsStream *s = dns_stream_ref(userdata); /* Protect stream while we process it */
+static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
bool progressed = false;
int r;
assert(s);
+ /* This returns 1 when possible remaining stream exists, 0 on completed
+ stream or recoverable error, and negative errno on failure. */
+
#if ENABLE_DNS_OVER_TLS
if (s->encrypted) {
r = dnstls_stream_on_io(s, revents);
@@ -441,6 +444,44 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use
log_warning_errno(errno, "Couldn't restart TCP connection timeout, ignoring: %m");
}
+ return 1;
+}
+
+static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_stream_unrefp) DnsStream *s = dns_stream_ref(userdata); /* Protect stream while we process it */
+ int r;
+
+ assert(s);
+
+ r = on_stream_io_impl(s, revents);
+ if (r <= 0)
+ return r;
+
+#if ENABLE_DNS_OVER_TLS
+ if (!s->encrypted)
+ return 0;
+
+ /* When using DNS-over-TLS, the underlying TLS library may read the entire TLS record
+ and buffer it internally. If this happens, we will not receive further EPOLLIN events,
+ and unless there's some unrelated activity on the socket, we will hang until time out.
+ To avoid this, if there's buffered TLS data, generate a "fake" EPOLLIN event.
+ This is hacky, but it makes this case transparent to the rest of the IO code. */
+ while (dnstls_stream_has_buffered_data(s)) {
+ uint32_t events;
+
+ /* Make sure the stream still wants to process more data... */
+ r = sd_event_source_get_io_events(s->io_event_source, &events);
+ if (r < 0)
+ return r;
+ if (!FLAGS_SET(events, EPOLLIN))
+ break;
+
+ r = on_stream_io_impl(s, EPOLLIN);
+ if (r <= 0)
+ return r;
+ }
+#endif
+
return 0;
}
diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c
index e7ccba934e..8610cacab6 100644
--- a/src/resolve/resolved-dnstls-gnutls.c
+++ b/src/resolve/resolved-dnstls-gnutls.c
@@ -211,6 +211,14 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) {
return ss;
}
+bool dnstls_stream_has_buffered_data(DnsStream *stream) {
+ assert(stream);
+ assert(stream->encrypted);
+ assert(stream->dnstls_data.session);
+
+ return gnutls_record_check_pending(stream->dnstls_data.session) > 0;
+}
+
void dnstls_server_free(DnsServer *server) {
assert(server);
diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c
index cba3f14f2d..7d264dd367 100644
--- a/src/resolve/resolved-dnstls-openssl.c
+++ b/src/resolve/resolved-dnstls-openssl.c
@@ -367,6 +367,14 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) {
return ss;
}
+bool dnstls_stream_has_buffered_data(DnsStream *stream) {
+ assert(stream);
+ assert(stream->encrypted);
+ assert(stream->dnstls_data.ssl);
+
+ return SSL_has_pending(stream->dnstls_data.ssl) > 0;
+}
+
void dnstls_server_free(DnsServer *server) {
assert(server);
diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h
index b638d61ec7..ed214dc6c4 100644
--- a/src/resolve/resolved-dnstls.h
+++ b/src/resolve/resolved-dnstls.h
@@ -3,6 +3,7 @@
#if ENABLE_DNS_OVER_TLS
+#include <stdbool.h>
#include <stdint.h>
typedef struct DnsServer DnsServer;
@@ -28,6 +29,7 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents);
int dnstls_stream_shutdown(DnsStream *stream, int error);
ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count);
ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count);
+bool dnstls_stream_has_buffered_data(DnsStream *stream);
void dnstls_server_free(DnsServer *server);
diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c
new file mode 100644
index 0000000000..fd7ade19d1
--- /dev/null
+++ b/src/resolve/test-resolved-stream.c
@@ -0,0 +1,342 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "log.h"
+#include "process-util.h"
+#include "resolved-dns-packet.h"
+#include "resolved-dns-question.h"
+#include "resolved-dns-rr.h"
+#if ENABLE_DNS_OVER_TLS
+#include "resolved-dnstls.h"
+#endif
+#include "resolved-dns-server.h"
+#include "resolved-dns-stream.h"
+#include "resolved-manager.h"
+#include "sd-event.h"
+#include "sparse-endian.h"
+#include "tests.h"
+
+static struct sockaddr_in SERVER_ADDRESS;
+
+/* Bytes of the questions & answers used in the test, including TCP DNS 2-byte length prefix */
+static const uint8_t QUESTION_A[] = {
+ 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 'e',
+ 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x01, 0x00, 0x01
+};
+static const uint8_t QUESTION_AAAA[] = {
+ 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 'e',
+ 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x1C, 0x00, 0x01
+};
+static const uint8_t ANSWER_A[] = {
+ 0x00, 0x2D, 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 'e',
+ 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0,
+ 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x52, 0x8D, 0x00, 0x04, 0x5D, 0xB8, 0xD8, 0x22,
+};
+static const uint8_t ANSWER_AAAA[] = {
+ 0x00, 0x39, 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 'e',
+ 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x1C, 0x00, 0x01, 0xC0,
+ 0x0C, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x54, 0x4B, 0x00, 0x10, 0x26, 0x06, 0x28, 0x00, 0x02,
+ 0x20, 0x00, 0x01, 0x02, 0x48, 0x18, 0x93, 0x25, 0xC8, 0x19, 0x46,
+};
+
+/**
+ * A mock TCP DNS server that asserts certain questions are received
+ * and replies with the same answer every time.
+ */
+static void receive_and_check_question(int fd, const uint8_t *expected_question,
+ size_t question_size) {
+ uint8_t *actual_question;
+ size_t n_read = 0;
+
+ actual_question = newa(uint8_t, question_size);
+ while (n_read < question_size) {
+ ssize_t r = read(fd, actual_question + n_read, question_size - n_read);
+ assert_se(r >= 0);
+ n_read += (size_t)r;
+ }
+ assert_se(n_read == question_size);
+
+ assert_se(memcmp(expected_question, actual_question, question_size) == 0);
+}
+
+static void send_answer(int fd, const uint8_t *answer, size_t answer_size) {
+ assert_se(write(fd, answer, answer_size) == (ssize_t)answer_size);
+}
+
+/* Sends two answers together in a single write operation,
+ * so they hopefully end up in a single TCP packet / TLS record */
+static void send_answers_together(int fd,
+ const uint8_t *answer1, size_t answer1_size,
+ const uint8_t *answer2, size_t answer2_size) {
+ uint8_t *answer;
+ size_t answer_size = answer1_size + answer2_size;
+
+ answer = newa(uint8_t, answer_size);
+ memcpy(answer, answer1, answer1_size);
+ memcpy(answer + answer1_size, answer2, answer2_size);
+ assert_se(write(fd, answer, answer_size) == (ssize_t)answer_size);
+}
+
+static void server_handle(int fd) {
+ receive_and_check_question(fd, QUESTION_A, sizeof(QUESTION_A));
+ send_answer(fd, ANSWER_A, sizeof(ANSWER_A));
+
+ receive_and_check_question(fd, QUESTION_AAAA, sizeof(QUESTION_AAAA));
+ send_answer(fd, ANSWER_AAAA, sizeof(ANSWER_AAAA));
+
+ receive_and_check_question(fd, QUESTION_A, sizeof(QUESTION_A));
+ receive_and_check_question(fd, QUESTION_AAAA, sizeof(QUESTION_AAAA));
+ send_answers_together(fd, ANSWER_A, sizeof(ANSWER_A),
+ ANSWER_AAAA, sizeof(ANSWER_AAAA));
+}
+
+static void *tcp_dns_server(void *p) {
+ _cleanup_close_ int bindfd = -1, acceptfd = -1;
+
+ assert_se((bindfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0);
+ assert_se(setsockopt(bindfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) >= 0);
+ assert_se(bind(bindfd, &SERVER_ADDRESS, sizeof(SERVER_ADDRESS)) >= 0);
+ assert_se(listen(bindfd, 1) >= 0);
+ assert_se((acceptfd = accept(bindfd, NULL, NULL)) >= 0);
+ server_handle(acceptfd);
+ return NULL;
+}
+
+#if ENABLE_DNS_OVER_TLS
+/*
+ * Spawns a DNS TLS server using the command line "openssl s_server" tool.
+ */
+static void *tls_dns_server(void *p) {
+ pid_t openssl_pid;
+ int r;
+ _cleanup_close_ int fd_server = -1, fd_tls = -1;
+ _cleanup_free_ char *cert_path = NULL, *key_path = NULL;
+ _cleanup_free_ char *ip_str = NULL, *bind_str = NULL;
+
+ assert_se(get_testdata_dir("test-resolve/selfsigned.cert", &cert_path) >= 0);
+ assert_se(get_testdata_dir("test-resolve/selfsigned.key", &key_path) >= 0);
+
+ assert_se(in_addr_to_string(SERVER_ADDRESS.sin_family,
+ &(union in_addr_union){.in = SERVER_ADDRESS.sin_addr},
+ &ip_str) >= 0);
+ asprintf(&bind_str, "%s:%d", ip_str, be16toh(SERVER_ADDRESS.sin_port));
+
+ /* We will hook one of the socketpair ends to OpenSSL's TLS server
+ * stdin/stdout, so we will be able to read and write plaintext
+ * from the other end's file descriptor like an usual TCP server */
+ {
+ int fd[2];
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) >= 0);
+ fd_server = fd[0];
+ fd_tls = fd[1];
+ }
+
+ r = safe_fork_full("(test-resolved-stream-tls-openssl)", (int[]) { fd_server, fd_tls }, 2,
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_REOPEN_LOG, &openssl_pid);
+ assert(r >= 0);
+ if (r == 0) {
+ /* Child */
+ assert_se(dup2(fd_tls, STDIN_FILENO) >= 0);
+ assert_se(dup2(fd_tls, STDOUT_FILENO) >= 0);
+ close(TAKE_FD(fd_server));
+ close(TAKE_FD(fd_tls));
+
+ execlp("openssl", "openssl", "s_server", "-accept", bind_str,
+ "-key", key_path, "-cert", cert_path,
+ "-quiet", "-naccept", "1", NULL);
+ log_error("exec failed, is something wrong with the 'openssl' command?");
+ _exit(EXIT_FAILURE);
+ } else {
+ pthread_mutex_t *server_lock = (pthread_mutex_t *)p;
+
+ server_handle(fd_server);
+
+ /* Once the test is done kill the TLS server to release the port */
+ assert_se(pthread_mutex_lock(server_lock) == 0);
+ assert_se(kill(openssl_pid, SIGTERM) >= 0);
+ assert_se(waitpid(openssl_pid, NULL, 0) >= 0);
+ assert_se(pthread_mutex_unlock(server_lock) == 0);
+ }
+
+ return NULL;
+}
+#endif
+
+static const char *TEST_DOMAIN = "example.com";
+static const uint64_t EVENT_TIMEOUT_USEC = 5 * 1000 * 1000;
+
+static void send_simple_question(DnsStream *stream, uint16_t type) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+
+ assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX) >= 0);
+ assert_se(question = dns_question_new(1));
+ assert_se(key = dns_resource_key_new(DNS_CLASS_IN, type, TEST_DOMAIN));
+ assert_se(dns_question_add(question, key, 0) >= 0);
+ assert_se(dns_packet_append_question(p, question) >= 0);
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(question));
+ assert_se(dns_stream_write_packet(stream, p) >= 0);
+}
+
+static const size_t MAX_RECEIVED_PACKETS = 2;
+static DnsPacket *received_packets[2] = {};
+static size_t n_received_packets = 0;
+
+static int on_stream_packet(DnsStream *stream) {
+ assert_se(n_received_packets < MAX_RECEIVED_PACKETS);
+ assert_se(received_packets[n_received_packets++] = dns_stream_take_read_packet(stream));
+ return 0;
+}
+
+static void test_dns_stream(bool tls) {
+ Manager manager = {};
+ _cleanup_(dns_stream_unrefp) DnsStream *stream = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_close_ int clientfd = -1;
+ int r;
+
+ void *(*server_entrypoint)(void *);
+ pthread_t server_thread;
+ pthread_mutex_t server_lock;
+
+ log_info("test-resolved-stream: Started %s test", tls ? "TLS" : "TCP");
+
+#if ENABLE_DNS_OVER_TLS
+ if (tls) {
+ /* For TLS mode, use DNS_OVER_TLS_OPPORTUNISTIC instead of
+ * DNS_OVER_TLS_YES, just to make certificate validation more
+ * lenient, allowing us to use self-signed certificates.
+ * We never downgrade, everything we test always goes over TLS */
+ manager.dns_over_tls_mode = DNS_OVER_TLS_OPPORTUNISTIC;
+ }
+#endif
+
+ assert_se(sd_event_new(&event) >= 0);
+ manager.event = event;
+
+ /* Set up a mock DNS (over TCP or TLS) server */
+ server_entrypoint = tcp_dns_server;
+#if ENABLE_DNS_OVER_TLS
+ if (tls)
+ server_entrypoint = tls_dns_server;
+#endif
+ assert_se(pthread_mutex_init(&server_lock, NULL) == 0);
+ assert_se(pthread_mutex_lock(&server_lock) == 0);
+ assert_se(pthread_create(&server_thread, NULL, server_entrypoint, &server_lock) == 0);
+
+ /* Create a socket client and connect to the TCP or TLS server
+ * The server may not be up immediately, so try to connect a few times before failing */
+ assert_se((clientfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0);
+
+ for (int i = 0; i < 100; i++) {
+ r = connect(clientfd, &SERVER_ADDRESS, sizeof(SERVER_ADDRESS));
+ if (r >= 0)
+ break;
+ usleep(EVENT_TIMEOUT_USEC / 100);
+ }
+ assert_se(r >= 0);
+
+ /* systemd-resolved uses (and requires) the socket to be in nonblocking mode */
+ assert_se(fcntl(clientfd, F_SETFL, O_NONBLOCK) >= 0);
+
+ /* Initialize DNS stream */
+ assert_se(dns_stream_new(&manager, &stream, DNS_STREAM_LOOKUP, DNS_PROTOCOL_DNS,
+ TAKE_FD(clientfd), NULL, DNS_STREAM_DEFAULT_TIMEOUT_USEC) >= 0);
+ stream->on_packet = on_stream_packet;
+#if ENABLE_DNS_OVER_TLS
+ if (tls) {
+ DnsServer server = {
+ .manager = &manager,
+ .family = SERVER_ADDRESS.sin_family,
+ .address.in = SERVER_ADDRESS.sin_addr
+ };
+
+ assert_se(dnstls_manager_init(&manager) >= 0);
+ assert_se(dnstls_stream_connect_tls(stream, &server) >= 0);
+ }
+#endif
+
+ /* Test: Question of type A and associated answer */
+ log_info("test-resolved-stream: A record");
+ send_simple_question(stream, DNS_TYPE_A);
+ while (n_received_packets != 1)
+ assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1);
+ assert_se(DNS_PACKET_DATA(received_packets[0]));
+ assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]),
+ ANSWER_A + 2, sizeof(ANSWER_A) - 2) == 0);
+ dns_packet_unref(TAKE_PTR(received_packets[0]));
+ n_received_packets = 0;
+
+ /* Test: Question of type AAAA and associated answer */
+ log_info("test-resolved-stream: AAAA record");
+ send_simple_question(stream, DNS_TYPE_AAAA);
+ while (n_received_packets != 1)
+ assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1);
+ assert_se(DNS_PACKET_DATA(received_packets[0]));
+ assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]),
+ ANSWER_AAAA + 2, sizeof(ANSWER_AAAA) - 2) == 0);
+ dns_packet_unref(TAKE_PTR(received_packets[0]));
+ n_received_packets = 0;
+
+ /* Test: Question of type A and AAAA and associated answers
+ * Both answers are sent back in a single packet or TLS record
+ * (tests the fix of PR #22132: "Fix DoT timeout on multiple answer records") */
+ log_info("test-resolved-stream: A + AAAA record");
+ send_simple_question(stream, DNS_TYPE_A);
+ send_simple_question(stream, DNS_TYPE_AAAA);
+
+ while (n_received_packets != 2)
+ assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1);
+ assert_se(DNS_PACKET_DATA(received_packets[0]));
+ assert_se(DNS_PACKET_DATA(received_packets[1]));
+ assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]),
+ ANSWER_A + 2, sizeof(ANSWER_A) - 2) == 0);
+ assert_se(memcmp(DNS_PACKET_DATA(received_packets[1]),
+ ANSWER_AAAA + 2, sizeof(ANSWER_AAAA) - 2) == 0);
+ dns_packet_unref(TAKE_PTR(received_packets[0]));
+ dns_packet_unref(TAKE_PTR(received_packets[1]));
+ n_received_packets = 0;
+
+#if ENABLE_DNS_OVER_TLS
+ if (tls)
+ dnstls_manager_free(&manager);
+#endif
+
+ /* Stop the DNS server */
+ assert_se(pthread_mutex_unlock(&server_lock) == 0);
+ assert_se(pthread_join(server_thread, NULL) == 0);
+ assert_se(pthread_mutex_destroy(&server_lock) == 0);
+
+ log_info("test-resolved-stream: Finished %s test", tls ? "TLS" : "TCP");
+}
+
+int main(int argc, char **argv) {
+ SERVER_ADDRESS = (struct sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_port = htobe16(12345),
+ .sin_addr.s_addr = htobe32(INADDR_LOOPBACK)
+ };
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_dns_stream(false);
+#if ENABLE_DNS_OVER_TLS
+ if (system("openssl version >/dev/null 2>&1") != 0)
+ return log_tests_skipped("Skipping TLS test since the 'openssl' command does not seem to be available");
+ test_dns_stream(true);
+#endif
+
+ return 0;
+}
diff --git a/test/test-resolve/selfsigned.cert b/test/test-resolve/selfsigned.cert
new file mode 100644
index 0000000000..456862c220
--- /dev/null
+++ b/test/test-resolve/selfsigned.cert
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFmzCCA4OgAwIBAgIUZlvbV3+2YGjHJDTW+u0XL/ypvsowDQYJKoZIhvcNAQEL
+BQAwXDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By
+aW5nZmllbGQxDDAKBgNVBAoMA0RpczEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29t
+MCAXDTIyMDEyMzE0MjYyMloYDzMwMjEwNTI2MTQyNjIyWjBcMQswCQYDVQQGEwJV
+UzEPMA0GA1UECAwGRGVuaWFsMRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UE
+CgwDRGlzMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC14826tEEHKICM/AKOKsyBUDaa6Z6KqS927ifb43LJ
+fxtg8vW+vX9OGtje2qVAoI1UMSu4yttItSd9cjrnAFLPFvQGC8dFhn436ehLWiKP
+AP5KvhIQ3equ5fTicn+Hxdm7C3Um2SEEE347I/vArfzuNE7PIJ57sh7KeBHGCrU3
+6iPl1DkkUilbqJAcgFoepozx1SbPq4h8LsdqJDKg+XUtvtuUS850D5Hb5ErTc8NL
+VrA+urSIr+yIX4jAeLXbktNLGuAc3+cJTjwuWdDvP51yS0qpj459dFaRWQE9gu0b
+DkRwYLF7Mpel66B8TBkHAWBhSs+oLNnv//Zv945wbUkTK29N2VtSEI4pd/47nTX9
+MwGn4q/ZAjhI7JUN3LcRDsrLdVAUabbK/U+xkL/lOlRRBK/1iuLELkaJlMUiuqZh
+q3DvNjqeT5yY8GTU5iXoBcvY0lac3+zYaemTgD5cZfF4gpTflGfc5Gf+he6U3Dol
+TT+4JfMrw0YdbqsH4oyEtmLBfMvvp+PQysiOELSFbSAphZOOcy8QSzoRrniNynPd
+kM7kIM+0t2XUaz0lKtNuZSo9DnhTMvTLPnnbk5aJt5nPxPprcdqhcJhrHw7gVhBo
+EceYJmXGiJJMLYuBNymZ4u7YrBg0e0qO+Fi9a4Kfh/QNMq/6VrpWvycb9LtCLhU+
+qQIDAQABo1MwUTAdBgNVHQ4EFgQU3ugK1HtfPaq90JC5Qf5ekrn4uUcwHwYDVR0j
+BBgwFoAU3ugK1HtfPaq90JC5Qf5ekrn4uUcwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEATzNvQP+VNLY0qK5b/8j8C5PHB0WKsTHSVNOhITr0Bq67
+AeOq5Mh2qZvJd/QAPTb/Cg60802RLQxp6uhCcfMdxTXkz1mxq6dKEeDAu/zAZzXk
+WSpJl/VORnibjvXf2OS6ucb4KPOxfkYiD328CdSYBJapmQbnUmwZph2SO0bpY7K3
+EbTY9fIyabGMrjbXL5EGRvqA0NSnJHVUYx1h3b32PYKHrQKu6syCE4OrMY0yjdLH
+1WnHeC3iB02AFy7TTfmeUiMTxaiPXAPjBDDIQtv1GHt8GR7WHSD3seIhAu6Lzbyr
+0zrxk52C9v0YP1lgOwnvmQfbUSpWc29yhrXFkqkZToqbmYjNO55gPN8JA/2GrWan
+s8gQwQ8z+yWAqNJQA5S+9+hNlBlcq659gCjIxoyCmkol4EepwR1WWdZjs2I00FHk
+mQL1ig6H81rg/Bh2SraKR1tGdmjCNFi4RfWHsxCBcd1cGFeUIN+ygNmjXmzXJDFP
+5vUXL9J5iu+WD1rnwB2gPRSvZUrmKUZnOGk0/kt1RpgbcFdOza+6vZmB51fXZYpD
+YyvXHTbuHVOyXA160/Fmg6BNy5BfrTuXaZ3YVeZmvDf+ywVl2BFDQZDoLLQMIHzl
+L2DdMuhVmgITqx8ZtrSxqBxW0DQXFZiMT+sv81+o2SO5nDzSYjoXfQv/Xkgpx44=
+-----END CERTIFICATE-----
diff --git a/test/test-resolve/selfsigned.key b/test/test-resolve/selfsigned.key
new file mode 100644
index 0000000000..44a09829ef
--- /dev/null
+++ b/test/test-resolve/selfsigned.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+VULYeUwXocBKk6rBSgDlsFF5LW22
+-----END PRIVATE KEY-----