diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index f48e2a80298..51ffa6b4b05 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 e7ccba934e5..8610cacab67 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 cba3f14f2d9..7d264dd3673 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 b638d61ec7a..ed214dc6c46 100644 --- a/src/resolve/resolved-dnstls.h +++ b/src/resolve/resolved-dnstls.h @@ -3,6 +3,7 @@ #if ENABLE_DNS_OVER_TLS +#include #include 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);