diff --git a/src/resolve/meson.build b/src/resolve/meson.build index ad6a64dd60..4ea7b3a4ca 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -123,6 +123,9 @@ executables += [ resolve_test_template + { 'sources' : files('test-resolved-etc-hosts.c'), }, + resolve_test_template + { + 'sources' : files('test-resolved-manager.c'), + }, resolve_test_template + { 'sources' : files('test-resolved-packet.c'), }, diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index faf539195b..10b3b5977f 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -956,6 +956,13 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { assert(ret); ms = next_datagram_size_fd(fd); + /* The MSG_PEEK recv() in next_datagram_size_fd() validates the datagram's checksum, and drops it if + * it is bad. Hence we can end up here with nothing left to read even though the event source said + * otherwise, and must treat that like the recvmsg_safe() below does: as "no packet", not as a + * failure. Our callers propagate a failure to the event loop, which would then turn the listening + * socket's event source off for good. */ + if (ERRNO_IS_NEG_TRANSIENT(ms)) + return 0; if (ms < 0) return ms; diff --git a/src/resolve/test-resolved-manager.c b/src/resolve/test-resolved-manager.c new file mode 100644 index 0000000000..6b4e9e3e62 --- /dev/null +++ b/src/resolve/test-resolved-manager.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "dns-packet.h" +#include "fd-util.h" +#include "resolved-manager.h" +#include "tests.h" + +/* ================================================================ + * manager_recv() + * ================================================================ */ + +TEST(manager_recv_nothing_to_read) { + Manager manager = {}; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + _cleanup_close_ int fd = -EBADF; + + /* The event source may hand us a socket whose datagram is gone by the time we look at it: the + * MSG_PEEK recv() in next_datagram_size_fd() verifies the UDP checksum and drops the datagram if it + * is bad. That must be reported as "no packet", not as an error, as our callers pass an error on to + * the event loop, which then turns the listening socket's event source off. An empty non-blocking + * socket puts us in exactly that situation. */ + + fd = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); + ASSERT_OK_ERRNO(fd); + + ASSERT_OK_ZERO(manager_recv(&manager, fd, DNS_PROTOCOL_DNS, &p)); + ASSERT_NULL(p); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);