From 6016fcb0ea9eea232f55a8b27b5a7fa69c0b4e26 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Mon, 16 Jul 2018 23:34:29 -0700 Subject: [PATCH 1/4] resolved: refactor GnuTLS specific code in separate source file This is a first step towards supporting alternative TLS implementations for DNS-over-TLS. Co-authored-by: Filipe Brandenburger --- src/resolve/meson.build | 2 + src/resolve/resolved-dns-server.c | 9 +- src/resolve/resolved-dns-server.h | 10 +- src/resolve/resolved-dns-stream.c | 126 +++------------- src/resolve/resolved-dns-stream.h | 12 +- src/resolve/resolved-dns-transaction.c | 44 +----- src/resolve/resolved-dnstls-gnutls.c | 198 +++++++++++++++++++++++++ src/resolve/resolved-dnstls-gnutls.h | 21 +++ src/resolve/resolved-dnstls.h | 28 ++++ 9 files changed, 283 insertions(+), 167 deletions(-) create mode 100644 src/resolve/resolved-dnstls-gnutls.c create mode 100644 src/resolve/resolved-dnstls-gnutls.h create mode 100644 src/resolve/resolved-dnstls.h diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 15f3835d55..5955875f59 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -63,6 +63,7 @@ systemd_resolved_sources = files(''' resolved-dns-stub.c resolved-etc-hosts.h resolved-etc-hosts.c + resolved-dnstls.h '''.split()) resolvectl_sources = files(''' @@ -141,6 +142,7 @@ systemd_resolved_sources += [resolved_gperf_c, resolved_dnssd_gperf_c] systemd_resolved_dependencies = [threads, libgpg_error, libm, libidn] if conf.get('ENABLE_DNS_OVER_TLS') == 1 + systemd_resolved_sources += [files(['resolved-dnstls-gnutls.c', 'resolved-dnstls-gnutls.h'])] systemd_resolved_dependencies += [libgnutls] endif diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 6645db467c..eff3dbb275 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -81,8 +81,7 @@ int dns_server_new( s->linked = true; #if ENABLE_DNS_OVER_TLS - /* Do not verify cerificate */ - gnutls_certificate_allocate_credentials(&s->tls_cert_cred); + dnstls_server_init(s); #endif /* A new DNS server that isn't fallback is added and the one @@ -122,11 +121,7 @@ DnsServer* dns_server_unref(DnsServer *s) { dns_stream_unref(s->stream); #if ENABLE_DNS_OVER_TLS - if (s->tls_cert_cred) - gnutls_certificate_free_credentials(s->tls_cert_cred); - - if (s->tls_session_data.data) - gnutls_free(s->tls_session_data.data); + dnstls_server_free(s); #endif free(s->server_string); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 53d45a6361..fda1251049 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -3,10 +3,6 @@ #include "in-addr-util.h" -#if ENABLE_DNS_OVER_TLS -#include -#endif - typedef struct DnsServer DnsServer; typedef enum DnsServerType { @@ -41,6 +37,9 @@ int dns_server_feature_level_from_string(const char *s) _pure_; #include "resolved-link.h" #include "resolved-manager.h" +#if ENABLE_DNS_OVER_TLS +#include "resolved-dnstls.h" +#endif struct DnsServer { Manager *manager; @@ -58,8 +57,7 @@ struct DnsServer { DnsStream *stream; #if ENABLE_DNS_OVER_TLS - gnutls_certificate_credentials_t tls_cert_cred; - gnutls_datum_t tls_session_data; + DnsTlsServerData dnstls_data; #endif DnsServerFeatureLevel verified_feature_level; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 066daef96e..ee392efb9d 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -11,8 +11,6 @@ #define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) #define DNS_STREAMS_MAX 128 -#define WRITE_TLS_DATA 1 - static void dns_stream_stop(DnsStream *s) { assert(s); @@ -45,14 +43,11 @@ static int dns_stream_complete(DnsStream *s, int error) { assert(s); #if ENABLE_DNS_OVER_TLS - if (s->tls_session && IN_SET(error, ETIMEDOUT, 0)) { + if (s->encrypted) { int r; - r = gnutls_bye(s->tls_session, GNUTLS_SHUT_RDWR); - if (r == GNUTLS_E_AGAIN && !s->tls_bye) { - dns_stream_ref(s); /* keep reference for closing TLS session */ - s->tls_bye = true; - } else + r = dnstls_stream_shutdown(s, error); + if (r != -EAGAIN) dns_stream_stop(s); } else #endif @@ -191,32 +186,22 @@ static int dns_stream_identify(DnsStream *s) { return 0; } -static ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt, int flags) { +ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt, int flags) { ssize_t r; assert(s); assert(iov); #if ENABLE_DNS_OVER_TLS - if (s->tls_session && !(flags & WRITE_TLS_DATA)) { + if (s->encrypted && !(flags & DNS_STREAM_WRITE_TLS_DATA)) { ssize_t ss; size_t i; r = 0; for (i = 0; i < iovcnt; i++) { - ss = gnutls_record_send(s->tls_session, iov[i].iov_base, iov[i].iov_len); - if (ss < 0) { - switch(ss) { - - case GNUTLS_E_INTERRUPTED: - return -EINTR; - case GNUTLS_E_AGAIN: - return -EAGAIN; - default: - log_debug("Failed to invoke gnutls_record_send: %s", gnutls_strerror(ss)); - return -EIO; - } - } + ss = dnstls_stream_write(s, iov[i].iov_base, iov[i].iov_len); + if (ss < 0) + return ss; r += ss; if (ss != (ssize_t) iov[i].iov_len) @@ -258,28 +243,9 @@ static ssize_t dns_stream_read(DnsStream *s, void *buf, size_t count) { ssize_t ss; #if ENABLE_DNS_OVER_TLS - if (s->tls_session) { - ss = gnutls_record_recv(s->tls_session, buf, count); - if (ss < 0) { - switch(ss) { - - case GNUTLS_E_INTERRUPTED: - return -EINTR; - case GNUTLS_E_AGAIN: - return -EAGAIN; - default: - log_debug("Failed to invoke gnutls_record_send: %s", gnutls_strerror(ss)); - return -EIO; - } - } else if (s->on_connection) { - int r; - - r = s->on_connection(s); - s->on_connection = NULL; /* only call once */ - if (r < 0) - return r; - } - } else + if (s->encrypted) + ss = dnstls_stream_read(s, buf, count); + else #endif { ss = read(s->fd, buf, count); @@ -290,22 +256,6 @@ static ssize_t dns_stream_read(DnsStream *s, void *buf, size_t count) { return ss; } -#if ENABLE_DNS_OVER_TLS -static ssize_t dns_stream_tls_writev(gnutls_transport_ptr_t p, const giovec_t * iov, int iovcnt) { - int r; - - assert(p); - - r = dns_stream_writev((DnsStream*) p, (struct iovec*) iov, iovcnt, WRITE_TLS_DATA); - if (r < 0) { - errno = -r; - return -1; - } - - return r; -} -#endif - static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { DnsStream *s = userdata; @@ -321,36 +271,13 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use assert(s); #if ENABLE_DNS_OVER_TLS - if (s->tls_bye) { - assert(s->tls_session); - - r = gnutls_bye(s->tls_session, GNUTLS_SHUT_RDWR); - if (r != GNUTLS_E_AGAIN) { - s->tls_bye = false; - dns_stream_unref(s); - } - - return 0; - } - - if (s->tls_handshake < 0) { - assert(s->tls_session); - - s->tls_handshake = gnutls_handshake(s->tls_session); - if (s->tls_handshake >= 0) { - if (s->on_connection && !(gnutls_session_get_flags(s->tls_session) & GNUTLS_SFLAGS_FALSE_START)) { - r = s->on_connection(s); - s->on_connection = NULL; /* only call once */ - if (r < 0) - return r; - } - } else { - if (gnutls_error_is_fatal(s->tls_handshake)) - return dns_stream_complete(s, ECONNREFUSED); - else - return 0; - } + if (s->encrypted) { + r = dnstls_stream_on_io(s); + if (r == DNSTLS_STREAM_CLOSED || r == -EAGAIN) + return 0; + else if (r < 0) + return dns_stream_complete(s, -r); } #endif @@ -506,8 +433,8 @@ DnsStream *dns_stream_unref(DnsStream *s) { } #if ENABLE_DNS_OVER_TLS - if (s->tls_session) - gnutls_deinit(s->tls_session); + if (s->encrypted) + dnstls_stream_free(s); #endif ORDERED_SET_FOREACH(p, s->write_queue, i) @@ -586,21 +513,6 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd, co return 0; } -#if ENABLE_DNS_OVER_TLS -int dns_stream_connect_tls(DnsStream *s, gnutls_session_t tls_session) { - gnutls_transport_set_ptr2(tls_session, (gnutls_transport_ptr_t) (long) s->fd, s); - gnutls_transport_set_vec_push_function(tls_session, &dns_stream_tls_writev); - - s->encrypted = true; - s->tls_session = tls_session; - s->tls_handshake = gnutls_handshake(tls_session); - if (s->tls_handshake < 0 && gnutls_error_is_fatal(s->tls_handshake)) - return -ECONNREFUSED; - - return 0; -} -#endif - int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { int r; diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index 9a0da226d8..d64dc3b1d4 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -8,11 +8,12 @@ typedef struct DnsStream DnsStream; #include "resolved-dns-packet.h" #include "resolved-dns-transaction.h" #include "resolved-manager.h" - #if ENABLE_DNS_OVER_TLS -#include +#include "resolved-dnstls.h" #endif +#define DNS_STREAM_WRITE_TLS_DATA 1 + /* Streams are used by three subsystems: * * 1. The normal transaction logic when doing a DNS or LLMNR lookup via TCP @@ -40,9 +41,7 @@ struct DnsStream { socklen_t tfo_salen; #if ENABLE_DNS_OVER_TLS - gnutls_session_t tls_session; - int tls_handshake; - bool tls_bye; + DnsTlsStreamData dnstls_data; #endif sd_event_source *io_event_source; @@ -69,7 +68,7 @@ struct DnsStream { int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd, const union sockaddr_union *tfo_address); #if ENABLE_DNS_OVER_TLS -int dns_stream_connect_tls(DnsStream *s, gnutls_session_t tls_session); +int dns_stream_connect_tls(DnsStream *s, void *tls_session); #endif DnsStream *dns_stream_unref(DnsStream *s); DnsStream *dns_stream_ref(DnsStream *s); @@ -77,6 +76,7 @@ DnsStream *dns_stream_ref(DnsStream *s); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref); int dns_stream_write_packet(DnsStream *s, DnsPacket *p); +ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt, int flags); static inline bool DNS_STREAM_QUEUED(DnsStream *s) { assert(s); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 89d311811a..db30997d04 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -11,11 +11,10 @@ #include "resolved-dns-cache.h" #include "resolved-dns-transaction.h" #include "resolved-llmnr.h" -#include "string-table.h" - #if ENABLE_DNS_OVER_TLS -#include +#include "resolved-dnstls.h" #endif +#include "string-table.h" #define TRANSACTIONS_MAX 4096 #define TRANSACTION_TCP_TIMEOUT_USEC (10U*USEC_PER_SEC) @@ -503,20 +502,6 @@ static int dns_transaction_on_stream_packet(DnsTransaction *t, DnsPacket *p) { return 0; } -static int on_stream_connection(DnsStream *s) { -#if ENABLE_DNS_OVER_TLS - /* Store TLS Ticket for faster succesive TLS handshakes */ - if (s->tls_session && s->server) { - if (s->server->tls_session_data.data) - gnutls_free(s->server->tls_session_data.data); - - gnutls_session_get_data2(s->tls_session, &s->server->tls_session_data); - } -#endif - - return 0; -} - static int on_stream_complete(DnsStream *s, int error) { _cleanup_(dns_stream_unrefp) DnsStream *p = NULL; DnsTransaction *t, *n; @@ -578,9 +563,6 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { _cleanup_(dns_stream_unrefp) DnsStream *s = NULL; union sockaddr_union sa; int r; -#if ENABLE_DNS_OVER_TLS - gnutls_session_t gs; -#endif assert(t); @@ -655,32 +637,12 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { #if ENABLE_DNS_OVER_TLS if (DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level)) { assert(t->server); - - r = gnutls_init(&gs, GNUTLS_CLIENT | GNUTLS_ENABLE_FALSE_START | GNUTLS_NONBLOCK); - if (r < 0) - return r; - - /* As DNS-over-TLS is a recent protocol, older TLS versions can be disabled */ - r = gnutls_priority_set_direct(gs, "NORMAL:-VERS-ALL:+VERS-TLS1.2", NULL); - if (r < 0) - return r; - - r = gnutls_credentials_set(gs, GNUTLS_CRD_CERTIFICATE, t->server->tls_cert_cred); - if (r < 0) - return r; - - if (t->server->tls_session_data.size > 0) - gnutls_session_set_data(gs, t->server->tls_session_data.data, t->server->tls_session_data.size); - - gnutls_handshake_set_timeout(gs, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - - r = dns_stream_connect_tls(s, gs); + r = dnstls_stream_connect_tls(s, t->server); if (r < 0) return r; } #endif - s->on_connection = on_stream_connection; s->complete = on_stream_complete; s->on_packet = dns_stream_on_packet; diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c new file mode 100644 index 0000000000..9c348cef67 --- /dev/null +++ b/src/resolve/resolved-dnstls-gnutls.c @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS +#error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available. +#endif + +#include "resolved-dnstls.h" +#include "resolved-dns-stream.h" + +#include + +DEFINE_TRIVIAL_CLEANUP_FUNC(gnutls_session_t, gnutls_deinit); + +static ssize_t dnstls_stream_writev(gnutls_transport_ptr_t p, const giovec_t *iov, int iovcnt) { + int r; + + assert(p); + + r = dns_stream_writev((DnsStream*) p, (const struct iovec*) iov, iovcnt, DNS_STREAM_WRITE_TLS_DATA); + if (r < 0) { + errno = -r; + return -1; + } + + return r; +} + +int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { + _cleanup_(gnutls_deinitp) gnutls_session_t gs; + int r; + + assert(stream); + assert(server); + + r = gnutls_init(&gs, GNUTLS_CLIENT | GNUTLS_ENABLE_FALSE_START | GNUTLS_NONBLOCK); + if (r < 0) + return r; + + /* As DNS-over-TLS is a recent protocol, older TLS versions can be disabled */ + r = gnutls_priority_set_direct(gs, "NORMAL:-VERS-ALL:+VERS-TLS1.2", NULL); + if (r < 0) + return r; + + r = gnutls_credentials_set(gs, GNUTLS_CRD_CERTIFICATE, server->dnstls_data.cert_cred); + if (r < 0) + return r; + + if (server->dnstls_data.session_data.size > 0) { + gnutls_session_set_data(gs, server->dnstls_data.session_data.data, server->dnstls_data.session_data.size); + + // Clear old session ticket + gnutls_free(server->dnstls_data.session_data.data); + server->dnstls_data.session_data.data = NULL; + server->dnstls_data.session_data.size = 0; + } + + gnutls_handshake_set_timeout(gs, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + gnutls_transport_set_ptr2(gs, (gnutls_transport_ptr_t) (long) stream->fd, stream); + gnutls_transport_set_vec_push_function(gs, &dnstls_stream_writev); + + stream->encrypted = true; + stream->dnstls_data.handshake = gnutls_handshake(gs); + if (stream->dnstls_data.handshake < 0 && gnutls_error_is_fatal(stream->dnstls_data.handshake)) + return -ECONNREFUSED; + + stream->dnstls_data.session = TAKE_PTR(gs); + + return 0; +} + +void dnstls_stream_free(DnsStream *stream) { + assert(stream); + assert(stream->encrypted); + + if (stream->dnstls_data.session) + gnutls_deinit(stream->dnstls_data.session); +} + +int dnstls_stream_on_io(DnsStream *stream) { + int r; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.session); + + if (stream->dnstls_data.shutdown) { + r = gnutls_bye(stream->dnstls_data.session, GNUTLS_SHUT_RDWR); + if (r == GNUTLS_E_AGAIN) + return -EAGAIN; + else if (r < 0) + log_debug("Failed to invoke gnutls_bye: %s", gnutls_strerror(r)); + + stream->dnstls_data.shutdown = false; + dns_stream_unref(stream); + return DNSTLS_STREAM_CLOSED; + } else if (stream->dnstls_data.handshake < 0) { + stream->dnstls_data.handshake = gnutls_handshake(stream->dnstls_data.session); + if (stream->dnstls_data.handshake == GNUTLS_E_AGAIN) + return -EAGAIN; + else if (stream->dnstls_data.handshake < 0) { + log_debug("Failed to invoke gnutls_handshake: %s", gnutls_strerror(stream->dnstls_data.handshake)); + if (gnutls_error_is_fatal(stream->dnstls_data.handshake)) + return -ECONNREFUSED; + } + } + + return 0; +} + +int dnstls_stream_shutdown(DnsStream *stream, int error) { + int r; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.session); + + /* Store TLS Ticket for faster succesive TLS handshakes */ + if (stream->server && stream->server->dnstls_data.session_data.size == 0 && stream->dnstls_data.handshake == GNUTLS_E_SUCCESS) + gnutls_session_get_data2(stream->dnstls_data.session, &stream->server->dnstls_data.session_data); + + if (IN_SET(error, ETIMEDOUT, 0)) { + r = gnutls_bye(stream->dnstls_data.session, GNUTLS_SHUT_RDWR); + if (r == GNUTLS_E_AGAIN) { + if (!stream->dnstls_data.shutdown) { + stream->dnstls_data.shutdown = true; + dns_stream_ref(stream); + return -EAGAIN; + } + } else if (r < 0) + log_debug("Failed to invoke gnutls_bye: %s", gnutls_strerror(r)); + } + + return 0; +} + +ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count) { + ssize_t ss; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.session); + assert(buf); + + ss = gnutls_record_send(stream->dnstls_data.session, buf, count); + if (ss < 0) + switch(ss) { + case GNUTLS_E_INTERRUPTED: + return -EINTR; + case GNUTLS_E_AGAIN: + return -EAGAIN; + default: + log_debug("Failed to invoke gnutls_record_send: %s", gnutls_strerror(ss)); + return -EPIPE; + } + + return ss; +} + +ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { + ssize_t ss; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.session); + assert(buf); + + ss = gnutls_record_recv(stream->dnstls_data.session, buf, count); + if (ss < 0) + switch(ss) { + case GNUTLS_E_INTERRUPTED: + return -EINTR; + case GNUTLS_E_AGAIN: + return -EAGAIN; + default: + log_debug("Failed to invoke gnutls_record_recv: %s", gnutls_strerror(ss)); + return -EPIPE; + } + + return ss; +} + +void dnstls_server_init(DnsServer *server) { + assert(server); + + /* Do not verify cerificate */ + gnutls_certificate_allocate_credentials(&server->dnstls_data.cert_cred); +} + +void dnstls_server_free(DnsServer *server) { + assert(server); + + if (server->dnstls_data.cert_cred) + gnutls_certificate_free_credentials(server->dnstls_data.cert_cred); + + if (server->dnstls_data.session_data.data) + gnutls_free(server->dnstls_data.session_data.data); +} diff --git a/src/resolve/resolved-dnstls-gnutls.h b/src/resolve/resolved-dnstls-gnutls.h new file mode 100644 index 0000000000..8309f8d121 --- /dev/null +++ b/src/resolve/resolved-dnstls-gnutls.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS +#error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available. +#endif + +#include + +#include + +struct DnsTlsServerData { + gnutls_certificate_credentials_t cert_cred; + gnutls_datum_t session_data; +}; + +struct DnsTlsStreamData { + gnutls_session_t session; + int handshake; + bool shutdown; +}; diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h new file mode 100644 index 0000000000..32befc6414 --- /dev/null +++ b/src/resolve/resolved-dnstls.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#if !ENABLE_DNS_OVER_TLS +#error This source file requires DNS-over-TLS to be enabled +#endif + +typedef struct DnsTlsServerData DnsTlsServerData; +typedef struct DnsTlsStreamData DnsTlsStreamData; + +#if HAVE_GNUTLS +#include "resolved-dnstls-gnutls.h" +#endif + +#include "resolved-dns-stream.h" +#include "resolved-dns-transaction.h" + +#define DNSTLS_STREAM_CLOSED 1 + +int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server); +void dnstls_stream_free(DnsStream *stream); +int dnstls_stream_on_io(DnsStream *stream); +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); + +void dnstls_server_init(DnsServer *server); +void dnstls_server_free(DnsServer *server); From ba6aaf572774a49ec2ece32fff492ed814d60922 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Thu, 26 Jul 2018 20:34:16 +0100 Subject: [PATCH 2/4] resolved: set io events after receiving EAGAIN for TLS During handshake and TLS session closing, messages needs to be exchanged. Therefore this patch overrides the requested IO events for the TCP stream when the TLS is waiting for sending or receiving of messages during theses periods. This fixes issues with correctly closing the TLS stream and prevents the handshake from hanging in rare cases (not seen yet). --- src/resolve/resolved-dns-stream.c | 17 +++++++++++++++-- src/resolve/resolved-dns-stream.h | 1 + src/resolve/resolved-dnstls-gnutls.c | 13 +++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index ee392efb9d..36205a895c 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -36,6 +36,12 @@ static int dns_stream_update_io(DnsStream *s) { if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) f |= EPOLLIN; +#if ENABLE_DNS_OVER_TLS + /* For handshake and clean closing purposes, TLS can override requested events */ + if (s->dnstls_events) + f = s->dnstls_events; +#endif + return sd_event_source_set_io_events(s->io_event_source, f); } @@ -274,10 +280,17 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use if (s->encrypted) { r = dnstls_stream_on_io(s); - if (r == DNSTLS_STREAM_CLOSED || r == -EAGAIN) + if (r == DNSTLS_STREAM_CLOSED) return 0; - else if (r < 0) + else if (r == -EAGAIN) + return dns_stream_update_io(s); + else if (r < 0) { return dns_stream_complete(s, -r); + } else { + r = dns_stream_update_io(s); + if (r < 0) + return r; + } } #endif diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index d64dc3b1d4..72db2a1a56 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -42,6 +42,7 @@ struct DnsStream { #if ENABLE_DNS_OVER_TLS DnsTlsStreamData dnstls_data; + int dnstls_events; #endif sd_event_source *io_event_source; diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c index 9c348cef67..36ee570b46 100644 --- a/src/resolve/resolved-dnstls-gnutls.c +++ b/src/resolve/resolved-dnstls-gnutls.c @@ -86,23 +86,28 @@ int dnstls_stream_on_io(DnsStream *stream) { if (stream->dnstls_data.shutdown) { r = gnutls_bye(stream->dnstls_data.session, GNUTLS_SHUT_RDWR); - if (r == GNUTLS_E_AGAIN) + if (r == GNUTLS_E_AGAIN) { + stream->dnstls_events = gnutls_record_get_direction(stream->dnstls_data.session) == 1 ? EPOLLOUT : EPOLLIN; return -EAGAIN; - else if (r < 0) + } else if (r < 0) log_debug("Failed to invoke gnutls_bye: %s", gnutls_strerror(r)); + stream->dnstls_events = 0; stream->dnstls_data.shutdown = false; dns_stream_unref(stream); return DNSTLS_STREAM_CLOSED; } else if (stream->dnstls_data.handshake < 0) { stream->dnstls_data.handshake = gnutls_handshake(stream->dnstls_data.session); - if (stream->dnstls_data.handshake == GNUTLS_E_AGAIN) + if (stream->dnstls_data.handshake == GNUTLS_E_AGAIN) { + stream->dnstls_events = gnutls_record_get_direction(stream->dnstls_data.session) == 1 ? EPOLLOUT : EPOLLIN; return -EAGAIN; - else if (stream->dnstls_data.handshake < 0) { + } else if (stream->dnstls_data.handshake < 0) { log_debug("Failed to invoke gnutls_handshake: %s", gnutls_strerror(stream->dnstls_data.handshake)); if (gnutls_error_is_fatal(stream->dnstls_data.handshake)) return -ECONNREFUSED; } + + stream->dnstls_events = 0; } return 0; From 096cbdce13b811f34726533d1a569912adc7cbec Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Thu, 26 Jul 2018 22:47:50 +0100 Subject: [PATCH 3/4] resolved: basic OpenSSL support for DNS-over-TLS This provides basic OpenSSL support without optimizations like TCP Fast Open and TLS Session Tickets. Notice only a single SSL library can be enabled at a time and therefore journald functions provided by GnuTLS will be disabled when using OpenSSL. Fixes #9531 --- README | 3 +- meson.build | 41 ++++- meson_options.txt | 4 +- src/resolve/meson.build | 11 +- src/resolve/resolved-dns-stream.c | 2 + src/resolve/resolved-dnstls-gnutls.c | 2 +- src/resolve/resolved-dnstls-gnutls.h | 2 +- src/resolve/resolved-dnstls-openssl.c | 216 ++++++++++++++++++++++++++ src/resolve/resolved-dnstls-openssl.h | 20 +++ src/resolve/resolved-dnstls.h | 6 +- 10 files changed, 294 insertions(+), 13 deletions(-) create mode 100644 src/resolve/resolved-dnstls-openssl.c create mode 100644 src/resolve/resolved-dnstls-openssl.h diff --git a/README b/README index c0b264abf4..a3cfcada77 100644 --- a/README +++ b/README @@ -154,7 +154,8 @@ REQUIREMENTS: libmicrohttpd (optional) libpython (optional) libidn2 or libidn (optional) - gnutls >= 3.1.4 (optional, >= 3.5.3 is necessary to support DNS-over-TLS) + gnutls >= 3.1.4 (optional, >= 3.5.3 is required to support DNS-over-TLS with gnutls) + openssl >= 1.1.0 (optional, required to support DNS-over-TLS with openssl) elfutils >= 158 (optional) polkit (optional) pkg-config diff --git a/meson.build b/meson.build index a123940f10..5fcc57520a 100644 --- a/meson.build +++ b/meson.build @@ -1013,6 +1013,18 @@ else endif conf.set10('HAVE_GNUTLS', have) +want_openssl = get_option('openssl') +if want_openssl != 'false' and not fuzzer_build + libopenssl = dependency('openssl', + version : '>= 1.1.0', + required : want_openssl == 'true') + have = libopenssl.found() +else + have = false + libopenssl = [] +endif +conf.set10('HAVE_OPENSSL', have) + want_elfutils = get_option('elfutils') if want_elfutils != 'false' and not fuzzer_build libdw = dependency('libdw', @@ -1136,15 +1148,30 @@ substs.set('DEFAULT_DNSSEC_MODE', default_dnssec) dns_over_tls = get_option('dns-over-tls') if dns_over_tls != 'false' - have = (conf.get('HAVE_GNUTLS') == 1 and - libgnutls.version().version_compare('>=3.5.3')) - if dns_over_tls == 'true' and not have - error('DNS-over-TLS support was requested, but dependencies are not available') + if dns_over_tls == 'openssl' + have_gnutls = false + else + have_gnutls = (conf.get('HAVE_GNUTLS') == 1 and libgnutls.version().version_compare('>= 3.5.3')) + if dns_over_tls == 'gnutls' and not have_gnutls + error('DNS-over-TLS support was requested with gnutls, but dependencies are not available') + endif endif + if dns_over_tls == 'gnutls' or have_gnutls + have_openssl = false + else + have_openssl = conf.get('HAVE_OPENSSL') == 1 + if dns_over_tls != 'auto' and not have_openssl + str = dns_over_tls == 'openssl' ? ' with openssl' : '' + error('DNS-over-TLS support was requested$0$, but dependencies are not available'.format(str)) + endif + endif + have = have_gnutls or have_openssl else - have = false + have = have_gnutls = have_openssl = false endif conf.set10('ENABLE_DNS_OVER_TLS', have) +conf.set10('DNS_OVER_TLS_USE_GNUTLS', have_gnutls) +conf.set10('DNS_OVER_TLS_USE_OPENSSL', have_openssl) default_dns_over_tls = get_option('default-dns-over-tls') if fuzzer_build @@ -2950,6 +2977,7 @@ foreach tuple : [ ['qrencode'], ['microhttpd'], ['gnutls'], + ['openssl'], ['libcurl'], ['idn'], ['libidn2'], @@ -2976,7 +3004,8 @@ foreach tuple : [ ['localed'], ['networkd'], ['resolve'], - ['DNS-over-TLS'], + ['DNS-over-TLS(gnutls)', conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1], + ['DNS-over-TLS(openssl)', conf.get('DNS_OVER_TLS_USE_OPENSSL') == 1], ['coredump'], ['polkit'], ['legacy pkla', install_polkit_pkla], diff --git a/meson_options.txt b/meson_options.txt index 4fdd0caf4a..a927473b9b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -195,7 +195,7 @@ option('default-dns-over-tls', type : 'combo', description : 'default DNS-over-TLS mode', choices : ['opportunistic', 'no'], value : 'no') -option('dns-over-tls', type : 'combo', choices : ['auto', 'true', 'false'], +option('dns-over-tls', type : 'combo', choices : ['auto', 'gnutls', 'openssl', 'true', 'false'], description : 'DNS-over-TLS support') option('dns-servers', type : 'string', description : 'space-separated list of default DNS servers', @@ -255,6 +255,8 @@ option('gcrypt', type : 'combo', choices : ['auto', 'true', 'false'], description : 'gcrypt support') option('gnutls', type : 'combo', choices : ['auto', 'true', 'false'], description : 'gnutls support') +option('openssl', type : 'combo', choices : ['auto', 'true', 'false'], + description : 'openssl support') option('elfutils', type : 'combo', choices : ['auto', 'true', 'false'], description : 'elfutils support') option('zlib', type : 'combo', choices : ['auto', 'true', 'false'], diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 5955875f59..6d03ebf7f4 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -142,8 +142,15 @@ systemd_resolved_sources += [resolved_gperf_c, resolved_dnssd_gperf_c] systemd_resolved_dependencies = [threads, libgpg_error, libm, libidn] if conf.get('ENABLE_DNS_OVER_TLS') == 1 - systemd_resolved_sources += [files(['resolved-dnstls-gnutls.c', 'resolved-dnstls-gnutls.h'])] - systemd_resolved_dependencies += [libgnutls] + if conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1 + systemd_resolved_sources += [files(['resolved-dnstls-gnutls.c', 'resolved-dnstls-gnutls.h'])] + systemd_resolved_dependencies += [libgnutls] + elif conf.get('DNS_OVER_TLS_USE_OPENSSL') == 1 + systemd_resolved_sources += [files(['resolved-dnstls-openssl.c', 'resolved-dnstls-openssl.h'])] + systemd_resolved_dependencies += [libopenssl] + else + error('unknown dependency for supporting DNS-over-TLS') + endif endif if conf.get('ENABLE_RESOLVE') == 1 diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 36205a895c..faf5e26ba4 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -234,6 +234,8 @@ ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt, r = -EAGAIN; } else if (errno == EINPROGRESS) r = -EAGAIN; + else + r = -errno; } else s->tfo_salen = 0; /* connection is made */ } else { diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c index 36ee570b46..5e6a899db8 100644 --- a/src/resolve/resolved-dnstls-gnutls.c +++ b/src/resolve/resolved-dnstls-gnutls.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ -#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS +#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_GNUTLS #error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available. #endif diff --git a/src/resolve/resolved-dnstls-gnutls.h b/src/resolve/resolved-dnstls-gnutls.h index 8309f8d121..364eea1a03 100644 --- a/src/resolve/resolved-dnstls-gnutls.h +++ b/src/resolve/resolved-dnstls-gnutls.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once -#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS +#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_GNUTLS #error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available. #endif diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c new file mode 100644 index 0000000000..d0a1bba773 --- /dev/null +++ b/src/resolve/resolved-dnstls-openssl.c @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_OPENSSL +#error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available. +#endif + +#include "resolved-dnstls.h" +#include "resolved-dns-stream.h" + +#include +#include + +DEFINE_TRIVIAL_CLEANUP_FUNC(SSL*, SSL_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(BIO*, BIO_free); + +int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { + _cleanup_(SSL_freep) SSL *s = NULL; + _cleanup_(BIO_freep) BIO *b = NULL; + + assert(stream); + assert(server); + + b = BIO_new_socket(stream->fd, 0); + if (!b) + return -ENOMEM; + + s = SSL_new(server->dnstls_data.ctx); + if (!s) + return -ENOMEM; + + SSL_set_connect_state(s); + SSL_set_bio(s, b, b); + b = NULL; + + /* DNS-over-TLS using OpenSSL doesn't support TCP Fast Open yet */ + connect(stream->fd, &stream->tfo_address.sa, stream->tfo_salen); + stream->tfo_salen = 0; + + stream->encrypted = true; + stream->dnstls_events = EPOLLOUT; + stream->dnstls_data.ssl = TAKE_PTR(s); + + return 0; +} + +void dnstls_stream_free(DnsStream *stream) { + assert(stream); + assert(stream->encrypted); + + if (stream->dnstls_data.ssl) + SSL_free(stream->dnstls_data.ssl); +} + +int dnstls_stream_on_io(DnsStream *stream) { + int r; + int error; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.ssl); + + if (stream->dnstls_data.shutdown) { + r = SSL_shutdown(stream->dnstls_data.ssl); + if (r == 0) + return -EAGAIN; + else if (r < 0) { + error = SSL_get_error(stream->dnstls_data.ssl, r); + if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + return -EAGAIN; + } else { + char errbuf[256]; + + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_shutdown: %s", errbuf); + } + } + + stream->dnstls_events = 0; + stream->dnstls_data.shutdown = false; + dns_stream_unref(stream); + return DNSTLS_STREAM_CLOSED; + } else if (stream->dnstls_data.handshake <= 0) { + stream->dnstls_data.handshake = SSL_do_handshake(stream->dnstls_data.ssl); + if (stream->dnstls_data.handshake <= 0) { + error = SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); + if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + return -EAGAIN; + } else { + char errbuf[256]; + + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_do_handshake: %s", errbuf); + return -ECONNREFUSED; + } + } + + stream->dnstls_events = 0; + } + + return 0; +} + +int dnstls_stream_shutdown(DnsStream *stream, int error) { + int r; + int ssl_error; + SSL_SESSION *s; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.ssl); + + if (error == ETIMEDOUT) { + r = SSL_shutdown(stream->dnstls_data.ssl); + if (r == 0) { + if (!stream->dnstls_data.shutdown) { + stream->dnstls_data.shutdown = true; + dns_stream_ref(stream); + } + return -EAGAIN; + } else if (r < 0) { + ssl_error = SSL_get_error(stream->dnstls_data.ssl, r); + if (IN_SET(ssl_error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + stream->dnstls_events = ssl_error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + if (!stream->dnstls_data.shutdown) { + stream->dnstls_data.shutdown = true; + dns_stream_ref(stream); + } + return -EAGAIN; + } else { + char errbuf[256]; + + ERR_error_string_n(ssl_error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_shutdown: %s", errbuf); + } + } + } + + return 0; +} + +ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count) { + int r; + int error; + ssize_t ss; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.ssl); + assert(buf); + + ss = r = SSL_write(stream->dnstls_data.ssl, buf, count); + if (r <= 0) { + error = SSL_get_error(stream->dnstls_data.ssl, ss); + if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + ss = -EAGAIN; + } else { + char errbuf[256]; + + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_read: %s", errbuf); + ss = -EPIPE; + } + } + + stream->dnstls_events = 0; + return ss; +} + +ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { + int r; + int error; + ssize_t ss; + + assert(stream); + assert(stream->encrypted); + assert(stream->dnstls_data.ssl); + assert(buf); + + ss = r = SSL_read(stream->dnstls_data.ssl, buf, count); + if (r <= 0) { + error = SSL_get_error(stream->dnstls_data.ssl, ss); + if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + ss = -EAGAIN; + } else { + char errbuf[256]; + + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_read: %s", errbuf); + ss = -EPIPE; + } + } + + stream->dnstls_events = 0; + return ss; +} + +void dnstls_server_init(DnsServer *server) { + assert(server); + + server->dnstls_data.ctx = SSL_CTX_new(TLS_client_method()); + if (server->dnstls_data.ctx) { + SSL_CTX_set_min_proto_version(server->dnstls_data.ctx, TLS1_2_VERSION); + SSL_CTX_set_options(server->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); + } +} + +void dnstls_server_free(DnsServer *server) { + assert(server); + + if (server->dnstls_data.ctx) + SSL_CTX_free(server->dnstls_data.ctx); +} diff --git a/src/resolve/resolved-dnstls-openssl.h b/src/resolve/resolved-dnstls-openssl.h new file mode 100644 index 0000000000..c92d2b2354 --- /dev/null +++ b/src/resolve/resolved-dnstls-openssl.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_OPENSSL +#error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available. +#endif + +#include + +#include + +struct DnsTlsServerData { + SSL_CTX *ctx; +}; + +struct DnsTlsStreamData { + int handshake; + bool shutdown; + SSL *ssl; +}; diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h index 32befc6414..52af3e9801 100644 --- a/src/resolve/resolved-dnstls.h +++ b/src/resolve/resolved-dnstls.h @@ -8,8 +8,12 @@ typedef struct DnsTlsServerData DnsTlsServerData; typedef struct DnsTlsStreamData DnsTlsStreamData; -#if HAVE_GNUTLS +#if DNS_OVER_TLS_USE_GNUTLS #include "resolved-dnstls-gnutls.h" +#elif DNS_OVER_TLS_USE_OPENSSL +#include "resolved-dnstls-openssl.h" +#else +#error Unknown dependency for supporting DNS-over-TLS #endif #include "resolved-dns-stream.h" From 04c4d9199e04eb0ec2527ef34f182072d11a9338 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Fri, 27 Jul 2018 00:01:58 +0100 Subject: [PATCH 4/4] resolved: TCP Fast Open and TLS Session Tickets for OpenSSL To decreae latency this add support for TFO and TLS Session Tickets. As OpenSSL wouldn't let you easily set a different function all written data is temporarily cached and therefore needs to be flushed after each SSL function which can write data. --- src/resolve/resolved-dns-stream.c | 2 +- src/resolve/resolved-dnstls-gnutls.c | 2 +- src/resolve/resolved-dnstls-openssl.c | 152 +++++++++++++++++++++++--- src/resolve/resolved-dnstls-openssl.h | 2 + src/resolve/resolved-dnstls.h | 2 +- 5 files changed, 142 insertions(+), 18 deletions(-) diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index faf5e26ba4..8c6f217ad9 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -280,7 +280,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use #if ENABLE_DNS_OVER_TLS if (s->encrypted) { - r = dnstls_stream_on_io(s); + r = dnstls_stream_on_io(s, revents); if (r == DNSTLS_STREAM_CLOSED) return 0; diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c index 5e6a899db8..820e1926fd 100644 --- a/src/resolve/resolved-dnstls-gnutls.c +++ b/src/resolve/resolved-dnstls-gnutls.c @@ -77,7 +77,7 @@ void dnstls_stream_free(DnsStream *stream) { gnutls_deinit(stream->dnstls_data.session); } -int dnstls_stream_on_io(DnsStream *stream) { +int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { int r; assert(stream); diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c index d0a1bba773..5dd7737337 100644 --- a/src/resolve/resolved-dnstls-openssl.c +++ b/src/resolve/resolved-dnstls-openssl.c @@ -13,31 +13,84 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SSL*, SSL_free); DEFINE_TRIVIAL_CLEANUP_FUNC(BIO*, BIO_free); +static int dnstls_flush_write_buffer(DnsStream *stream) { + ssize_t ss; + + assert(stream); + assert(stream->encrypted); + + if (stream->dnstls_data.write_buffer->length > 0) { + assert(stream->dnstls_data.write_buffer->data); + + struct iovec iov[1]; + iov[0].iov_base = stream->dnstls_data.write_buffer->data; + iov[0].iov_len = stream->dnstls_data.write_buffer->length; + ss = dns_stream_writev(stream, iov, 1, DNS_STREAM_WRITE_TLS_DATA); + if (ss < 0) { + if (ss == -EAGAIN) + stream->dnstls_events |= EPOLLOUT; + + return ss; + } else { + stream->dnstls_data.write_buffer->length -= ss; + stream->dnstls_data.write_buffer->data += ss; + + if (stream->dnstls_data.write_buffer->length > 0) { + stream->dnstls_events |= EPOLLOUT; + return -EAGAIN; + } + } + } + + return 0; +} + int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { _cleanup_(SSL_freep) SSL *s = NULL; - _cleanup_(BIO_freep) BIO *b = NULL; + _cleanup_(BIO_freep) BIO *rb = NULL; + _cleanup_(BIO_freep) BIO *wb = NULL; + int r; + int error; assert(stream); assert(server); - b = BIO_new_socket(stream->fd, 0); - if (!b) + rb = BIO_new_socket(stream->fd, 0); + if (!rb) return -ENOMEM; + wb = BIO_new(BIO_s_mem()); + if (!wb) + return -ENOMEM; + + BIO_get_mem_ptr(wb, &stream->dnstls_data.write_buffer); + s = SSL_new(server->dnstls_data.ctx); if (!s) return -ENOMEM; SSL_set_connect_state(s); - SSL_set_bio(s, b, b); - b = NULL; + SSL_set_session(s, server->dnstls_data.session); + SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb)); - /* DNS-over-TLS using OpenSSL doesn't support TCP Fast Open yet */ - connect(stream->fd, &stream->tfo_address.sa, stream->tfo_salen); - stream->tfo_salen = 0; + stream->dnstls_data.handshake = SSL_do_handshake(s); + if (stream->dnstls_data.handshake <= 0) { + error = SSL_get_error(s, stream->dnstls_data.handshake); + if (!IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + char errbuf[256]; + + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + log_debug("Failed to invoke SSL_do_handshake: %s", errbuf); + return -ECONNREFUSED; + } + } stream->encrypted = true; - stream->dnstls_events = EPOLLOUT; + + r = dnstls_flush_write_buffer(stream); + if (r < 0 && r != -EAGAIN) + return r; + stream->dnstls_data.ssl = TAKE_PTR(s); return 0; @@ -51,7 +104,7 @@ void dnstls_stream_free(DnsStream *stream) { SSL_free(stream->dnstls_data.ssl); } -int dnstls_stream_on_io(DnsStream *stream) { +int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { int r; int error; @@ -59,14 +112,25 @@ int dnstls_stream_on_io(DnsStream *stream) { assert(stream->encrypted); assert(stream->dnstls_data.ssl); + /* Flush write buffer when requested by OpenSSL ss*/ + if ((revents & EPOLLOUT) && (stream->dnstls_events & EPOLLOUT)) { + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + } + if (stream->dnstls_data.shutdown) { r = SSL_shutdown(stream->dnstls_data.ssl); - if (r == 0) - return -EAGAIN; - else if (r < 0) { + if (r <= 0) { error = SSL_get_error(stream->dnstls_data.ssl, r); - if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { - stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + if (r == 0 || IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { + if (r < 0) + stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + return -EAGAIN; } else { char errbuf[256]; @@ -76,6 +140,10 @@ int dnstls_stream_on_io(DnsStream *stream) { } } + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + stream->dnstls_events = 0; stream->dnstls_data.shutdown = false; dns_stream_unref(stream); @@ -86,6 +154,10 @@ int dnstls_stream_on_io(DnsStream *stream) { error = SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + return -EAGAIN; } else { char errbuf[256]; @@ -97,6 +169,9 @@ int dnstls_stream_on_io(DnsStream *stream) { } stream->dnstls_events = 0; + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; } return 0; @@ -111,6 +186,16 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { assert(stream->encrypted); assert(stream->dnstls_data.ssl); + if (stream->server) { + s = SSL_get1_session(stream->dnstls_data.ssl); + if (s) { + if (stream->server->dnstls_data.session) + SSL_SESSION_free(stream->server->dnstls_data.session); + + stream->server->dnstls_data.session = s; + } + } + if (error == ETIMEDOUT) { r = SSL_shutdown(stream->dnstls_data.ssl); if (r == 0) { @@ -118,11 +203,20 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { stream->dnstls_data.shutdown = true; dns_stream_ref(stream); } + + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + return -EAGAIN; } else if (r < 0) { ssl_error = SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(ssl_error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = ssl_error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + r = dnstls_flush_write_buffer(stream); + if (r < 0 && r != -EAGAIN) + return r; + if (!stream->dnstls_data.shutdown) { stream->dnstls_data.shutdown = true; dns_stream_ref(stream); @@ -135,6 +229,11 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { log_debug("Failed to invoke SSL_shutdown: %s", errbuf); } } + + stream->dnstls_events = 0; + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; } return 0; @@ -155,6 +254,10 @@ ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count) { error = SSL_get_error(stream->dnstls_data.ssl, ss); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + ss = -EAGAIN; } else { char errbuf[256]; @@ -166,6 +269,10 @@ ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count) { } stream->dnstls_events = 0; + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + return ss; } @@ -184,6 +291,12 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { error = SSL_get_error(stream->dnstls_data.ssl, ss); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; + + /* flush write buffer in cache of renegotiation */ + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + ss = -EAGAIN; } else { char errbuf[256]; @@ -195,6 +308,12 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { } stream->dnstls_events = 0; + + /* flush write buffer in cache of renegotiation */ + r = dnstls_flush_write_buffer(stream); + if (r < 0) + return r; + return ss; } @@ -213,4 +332,7 @@ void dnstls_server_free(DnsServer *server) { if (server->dnstls_data.ctx) SSL_CTX_free(server->dnstls_data.ctx); + + if (server->dnstls_data.session) + SSL_SESSION_free(server->dnstls_data.session); } diff --git a/src/resolve/resolved-dnstls-openssl.h b/src/resolve/resolved-dnstls-openssl.h index c92d2b2354..c57bc1c57c 100644 --- a/src/resolve/resolved-dnstls-openssl.h +++ b/src/resolve/resolved-dnstls-openssl.h @@ -11,10 +11,12 @@ struct DnsTlsServerData { SSL_CTX *ctx; + SSL_SESSION *session; }; struct DnsTlsStreamData { int handshake; bool shutdown; SSL *ssl; + BUF_MEM *write_buffer; }; diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h index 52af3e9801..fdd85eece6 100644 --- a/src/resolve/resolved-dnstls.h +++ b/src/resolve/resolved-dnstls.h @@ -23,7 +23,7 @@ typedef struct DnsTlsStreamData DnsTlsStreamData; int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server); void dnstls_stream_free(DnsStream *stream); -int dnstls_stream_on_io(DnsStream *stream); +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);