From a3ba95116ed04594ea59a8124bf781b30367a7a2 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 22 Jun 2021 17:01:35 +0200 Subject: [PATCH] Handle UDP send errors when sending DNS message larger than MTU When the fragmentation is disabled on UDP sockets, the uv_udp_send() call can fail with UV_EMSGSIZE for messages larger than path MTU. Previously, this error would end with just discarding the response. In this commit, a proper handling of such case is added and on such error, a new DNS response with truncated bit set is generated and sent to the client. This change allows us to disable the fragmentation on the UDP sockets again. --- lib/isc/netmgr/uverr2result.c | 2 ++ lib/ns/client.c | 39 ++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/isc/netmgr/uverr2result.c b/lib/isc/netmgr/uverr2result.c index 8a5c8f6699..2c484fa1a7 100644 --- a/lib/isc/netmgr/uverr2result.c +++ b/lib/isc/netmgr/uverr2result.c @@ -85,6 +85,8 @@ isc___nm_uverr2result(int uverr, bool dolog, const char *file, return (ISC_R_CANCELED); case UV_EOF: return (ISC_R_EOF); + case UV_EMSGSIZE: + return (ISC_R_MAXSIZE); default: if (dolog) { UNEXPECTED_ERROR( diff --git a/lib/ns/client.c b/lib/ns/client.c index 814581b8fa..5ef93af64b 100644 --- a/lib/ns/client.c +++ b/lib/ns/client.c @@ -257,13 +257,33 @@ client_senddone(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { REQUIRE(client->sendhandle == handle); CTRACE("senddone"); + + /* + * Set sendhandle to NULL, but don't detach it immediately, in + * case we need to retry the send. If we do resend, then + * sendhandle will be reattached. Whether or not we resend, + * we will then detach the handle from *this* send by detaching + * 'handle' directly below. + */ + client->sendhandle = NULL; + if (result != ISC_R_SUCCESS) { - ns_client_log(client, DNS_LOGCATEGORY_SECURITY, - NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3), - "send failed: %s", isc_result_totext(result)); + if (!TCP_CLIENT(client) && result == ISC_R_MAXSIZE) { + ns_client_log(client, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3), + "send exceeded maximum size: truncating"); + client->query.attributes &= ~NS_QUERYATTR_ANSWERED; + client->rcode_override = dns_rcode_noerror; + ns_client_error(client, ISC_R_MAXSIZE); + } else { + ns_client_log(client, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3), + "send failed: %s", + isc_result_totext(result)); + } } - isc_nmhandle_detach(&client->sendhandle); + isc_nmhandle_detach(&handle); } static void @@ -699,8 +719,9 @@ ns_client_dropport(in_port_t port) { void ns_client_error(ns_client_t *client, isc_result_t result) { + dns_message_t *message = NULL; dns_rcode_t rcode; - dns_message_t *message; + bool trunc = false; REQUIRE(NS_CLIENT_VALID(client)); @@ -714,6 +735,10 @@ ns_client_error(ns_client_t *client, isc_result_t result) { rcode = (dns_rcode_t)(client->rcode_override & 0xfff); } + if (result == ISC_R_MAXSIZE) { + trunc = true; + } + #if NS_CLIENT_DROPPORT /* * Don't send FORMERR to ports on the drop port list. @@ -810,7 +835,11 @@ ns_client_error(ns_client_t *client, isc_result_t result) { return; } } + message->rcode = rcode; + if (trunc) { + message->flags |= DNS_MESSAGEFLAG_TC; + } if (rcode == dns_rcode_formerr) { /*