* Even this makes a room to read in the stream, this does not call dns_stream_update(), hence
* EPOLLIN flag is not set automatically. So, to read further packets from the stream,
* dns_stream_update() must be called explicitly. Currently, this is only called from
- * on_stream_io_impl(), and there dns_stream_update() is called. */
+ * on_stream_io(), and there dns_stream_update() is called. */
if (!s->read_packet)
return NULL;
return TAKE_PTR(s->read_packet);
}
-static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
+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 */
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);
}
}
- if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) &&
- (!s->read_packet ||
- s->n_read < sizeof(s->read_size) + s->read_packet->size)) {
+ while ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) &&
+ (!s->read_packet ||
+ s->n_read < sizeof(s->read_size) + s->read_packet->size)) {
if (s->n_read < sizeof(s->read_size)) {
ssize_t ss;
if (ss < 0) {
if (!ERRNO_IS_TRANSIENT(ss))
return dns_stream_complete(s, -ss);
+ break;
} else if (ss == 0)
return dns_stream_complete(s, ECONNRESET);
else {
if (ss < 0) {
if (!ERRNO_IS_TRANSIENT(ss))
return dns_stream_complete(s, -ss);
+ break;
} else if (ss == 0)
return dns_stream_complete(s, ECONNRESET);
else
return dns_stream_complete(s, -r);
s->packet_received = true;
+
+ /* If we just disabled the read event, stop reading */
+ if (!FLAGS_SET(s->requested_events, EPOLLIN))
+ break;
}
}
}
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... */
- if (!FLAGS_SET(s->requested_events, EPOLLIN))
- break;
-
- r = on_stream_io_impl(s, EPOLLIN);
- if (r <= 0)
- return r;
- }
-#endif
-
return 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 we receive SSL_ERROR_WANT_READ here, there are two possible scenarios:
+ * OpenSSL needs to renegotiate (so we want to get an EPOLLIN event), or
+ * There is no more application data is available, so we can just return
+ And apparently there's no nice way to distinguish between the two.
+ To handle this, never set EPOLLIN and just continue as usual.
+ If OpenSSL really wants to read due to renegotiation, it will tell us
+ again on SSL_write (at which point we will request EPOLLIN force a read);
+ or we will just eventually read data anyway while we wait for a packet */
+ stream->dnstls_events = error == SSL_ERROR_WANT_READ ? 0 : EPOLLOUT;
ss = -EAGAIN;
} else if (error == SSL_ERROR_ZERO_RETURN) {
stream->dnstls_events = 0;
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);
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);
+ assert_se(r >= 0);
if (r == 0) {
/* Child */
assert_se(dup2(fd_tls, STDIN_FILENO) >= 0);
return 0;
}
+static int on_stream_complete_do_nothing(DnsStream *s, int error) {
+ return 0;
+}
+
static void test_dns_stream(bool tls) {
Manager manager = {};
_cleanup_(dns_stream_unrefp) DnsStream *stream = NULL;
/* systemd-resolved uses (and requires) the socket to be in nonblocking mode */
assert_se(fcntl(clientfd, F_SETFL, O_NONBLOCK) >= 0);
- /* Initialize DNS stream */
+ /* Initialize DNS stream (disabling the default self-destruction
+ behaviour when no complete callback is set) */
assert_se(dns_stream_new(&manager, &stream, DNS_STREAM_LOOKUP, DNS_PROTOCOL_DNS,
- TAKE_FD(clientfd), NULL, on_stream_packet, NULL,
+ TAKE_FD(clientfd), NULL, on_stream_packet, on_stream_complete_do_nothing,
DNS_STREAM_DEFAULT_TIMEOUT_USEC) >= 0);
#if ENABLE_DNS_OVER_TLS
if (tls) {