summaryrefslogtreecommitdiff
path: root/src/resolve/resolved-dns-stream.c
diff options
context:
space:
mode:
authorJoan Bruguera <joanbrugueram@gmail.com>2022-01-30 12:51:10 +0100
committerJoan Bruguera <joanbrugueram@gmail.com>2022-02-01 19:25:32 +0100
commit839a70c3534ce10ed7a66b5925f4570d88b2b64a (patch)
tree105dd504b6317311540e89b37f0086e264daff02 /src/resolve/resolved-dns-stream.c
parentaa892849d50e9dd5da03a628463ccf6c55ff1b44 (diff)
downloadsystemd-839a70c3534ce10ed7a66b5925f4570d88b2b64a.tar.gz
resolved: Read as much as possible per stream EPOLLIN event
In commit 2aaf6bb6e99b0f2bd73e0c49bef9e11a2844bf1a, an issue was fixed where systemd-resolved could get stuck for multiple seconds waiting for incoming data, since GnuTLS/OpenSSL can buffer a TLS record, so data could be available, but no EPOLLIN event would be generated. To fix this, a somewhat elaborate logic consisting on asking the TLS library whether it had buffered data, then "faking" an EPOLLIN event was implemented. However, there is a much simpler solution: Always read as much data as available (i.e. until we get an event like EAGAIN when trying to read) from the stream when we get an EPOLLIN event, instead of at most a single packet per event. This approach does not require asking the TLS library whether it has buffered data, and the logic is exactly the same for both the TCP and TLS case. test-resolved-stream is fixed to avoid a latent double free bug.
Diffstat (limited to 'src/resolve/resolved-dns-stream.c')
-rw-r--r--src/resolve/resolved-dns-stream.c55
1 files changed, 12 insertions, 43 deletions
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index 5c4a9ebb99..61e92bea83 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -279,7 +279,7 @@ static DnsPacket *dns_stream_take_read_packet(DnsStream *s) {
* 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;
@@ -294,15 +294,13 @@ static DnsPacket *dns_stream_take_read_packet(DnsStream *s) {
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);
@@ -354,9 +352,9 @@ static int on_stream_io_impl(DnsStream *s, uint32_t 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;
@@ -365,6 +363,7 @@ static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
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 {
@@ -418,6 +417,7 @@ static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
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
@@ -438,6 +438,10 @@ static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
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;
}
}
}
@@ -455,41 +459,6 @@ static int on_stream_io_impl(DnsStream *s, uint32_t revents) {
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;
}