}
static int dns_stream_update_io(DnsStream *s) {
- int f = 0;
+ uint32_t f = 0;
assert(s);
set_size(s->queries) < DNS_QUERIES_PER_STREAM)
f |= EPOLLIN;
+ s->requested_events = f;
+
#if ENABLE_DNS_OVER_TLS
/* For handshake and clean closing purposes, TLS can override requested events */
if (s->dnstls_events != 0)
assert(iov);
#if ENABLE_DNS_OVER_TLS
- if (s->encrypted && !(flags & DNS_STREAM_WRITE_TLS_DATA)) {
- ssize_t ss;
- size_t i;
-
- m = 0;
- for (i = 0; i < iovcnt; i++) {
- ss = dnstls_stream_write(s, iov[i].iov_base, iov[i].iov_len);
- if (ss < 0)
- return ss;
-
- m += ss;
- if (ss != (ssize_t) iov[i].iov_len)
- continue;
- }
- } else
+ if (s->encrypted && !(flags & DNS_STREAM_WRITE_TLS_DATA))
+ return dnstls_stream_writev(s, iov, iovcnt);
#endif
+
if (s->tfo_salen > 0) {
struct msghdr hdr = {
.msg_iov = (struct iovec*) iov,
}
static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
- DnsStream *s = userdata;
+ DnsStream *s = ASSERT_PTR(userdata);
+ return dns_stream_complete(s, ETIMEDOUT);
+}
+
+static DnsPacket *dns_stream_take_read_packet(DnsStream *s) {
assert(s);
- return dns_stream_complete(s, ETIMEDOUT);
+ /* Note, dns_stream_update() should be called after this is called. When this is called, the
+ * stream may be already full and the EPOLLIN flag is dropped from the stream IO event source.
+ * 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(), and there dns_stream_update() is called. */
+
+ if (!s->read_packet)
+ return NULL;
+
+ if (s->n_read < sizeof(s->read_size))
+ return NULL;
+
+ if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size))
+ return NULL;
+
+ s->n_read = 0;
+ 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 {
s->read_packet->family = s->peer.sa.sa_family;
s->read_packet->ttl = s->ttl;
s->read_packet->ifindex = s->ifindex;
- s->read_packet->timestamp = now(clock_boottime_or_monotonic());
+ s->read_packet->timestamp = now(CLOCK_BOOTTIME);
if (s->read_packet->family == AF_INET) {
s->read_packet->sender.in = s->peer.in.sin_addr;
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
s->n_read += ss;
}
- /* Are we done? If so, disable the event source for EPOLLIN */
- if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) {
- /* If there's a packet handler
- * installed, call that. Note that
- * this is optional... */
- if (s->on_packet) {
- r = s->on_packet(s);
- if (r < 0)
- return r;
- }
+ /* Are we done? If so, call the packet handler and re-enable EPOLLIN for the
+ * event source if necessary. */
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = dns_stream_take_read_packet(s);
+ if (p) {
+ assert(s->on_packet);
+ r = s->on_packet(s, p);
+ if (r < 0)
+ return r;
r = dns_stream_update_io(s);
if (r < 0)
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;
}
}
}
- /* Call "complete" callback if finished reading and writing one packet, and there's nothing else left
- * to write. */
- if (s->type == DNS_STREAM_LLMNR_SEND &&
- (s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) &&
- ordered_set_isempty(s->write_queue) &&
- (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size))
+ /* Complete the stream if finished reading and writing one packet, and there's nothing
+ * else left to write. */
+ if (s->type == DNS_STREAM_LLMNR_SEND && s->packet_received &&
+ !FLAGS_SET(s->requested_events, EPOLLOUT))
return dns_stream_complete(s, 0);
/* If we did something, let's restart the timeout event source */
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;
}
DnsProtocol protocol,
int fd,
const union sockaddr_union *tfo_address,
+ int (on_packet)(DnsStream*, DnsPacket*),
+ int (complete)(DnsStream*, int), /* optional */
usec_t connect_timeout_usec) {
_cleanup_(dns_stream_unrefp) DnsStream *s = NULL;
assert(protocol >= 0);
assert(protocol < _DNS_PROTOCOL_MAX);
assert(fd >= 0);
+ assert(on_packet);
if (m->n_dns_streams[type] > DNS_STREAMS_MAX)
return -EBUSY;
*s = (DnsStream) {
.n_ref = 1,
- .fd = -1,
+ .fd = -EBADF,
.protocol = protocol,
.type = type,
};
r = sd_event_add_time_relative(
m->event,
&s->timeout_event_source,
- clock_boottime_or_monotonic(),
+ CLOCK_BOOTTIME,
connect_timeout_usec, 0,
on_stream_timeout, s);
if (r < 0)
s->manager = m;
s->fd = fd;
+ s->on_packet = on_packet;
+ s->complete = complete;
if (tfo_address) {
s->tfo_address = *tfo_address;
return dns_stream_update_io(s);
}
-DnsPacket *dns_stream_take_read_packet(DnsStream *s) {
- assert(s);
-
- if (!s->read_packet)
- return NULL;
-
- if (s->n_read < sizeof(s->read_size))
- return NULL;
-
- if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size))
- return NULL;
-
- s->n_read = 0;
- return TAKE_PTR(s->read_packet);
-}
-
void dns_stream_detach(DnsStream *s) {
assert(s);