git: 7d9c0fb57893 - stable/12 - unbound: Import upstream 0ee44ef3 when ENOBUFS is returned

From: Cy Schubert <cy_at_FreeBSD.org>
Date: Mon, 16 Oct 2023 04:01:06 UTC
The branch stable/12 has been updated by cy:

URL: https://cgit.FreeBSD.org/src/commit/?id=7d9c0fb578931f7f622970bad40d799abd74a76f

commit 7d9c0fb578931f7f622970bad40d799abd74a76f
Author:     Cy Schubert <cy@FreeBSD.org>
AuthorDate: 2023-10-13 00:04:25 +0000
Commit:     Cy Schubert <cy@FreeBSD.org>
CommitDate: 2023-10-13 01:54:35 +0000

    unbound: Import upstream 0ee44ef3 when ENOBUFS is returned
    
    From upstream 0ee44ef3:
    
    - Fix send of udp retries when ENOBUFS is returned. It stops looping
      and also waits for the condition to go away. Reported by Florian
      Obser.
    
    PR:             274352
    
    Merge commit '292d51198aa319c58f534549851e9c28486abdf4'
    
    (cherry picked from commit 6e71235e558ef579605e7f35b02f983b9a246a4a)
---
 contrib/unbound/.gitignore      |   1 +
 contrib/unbound/util/netevent.c | 102 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/contrib/unbound/.gitignore b/contrib/unbound/.gitignore
new file mode 100644
index 000000000000..b25c15b81fae
--- /dev/null
+++ b/contrib/unbound/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/contrib/unbound/util/netevent.c b/contrib/unbound/util/netevent.c
index 204e4883cf27..b9395a8998b9 100644
--- a/contrib/unbound/util/netevent.c
+++ b/contrib/unbound/util/netevent.c
@@ -116,6 +116,8 @@
 
 /** timeout in millisec to wait for write to unblock, packets dropped after.*/
 #define SEND_BLOCKED_WAIT_TIMEOUT 200
+/** max number of times to wait for write to unblock, packets dropped after.*/
+#define SEND_BLOCKED_MAX_RETRY 5
 
 /** Let's make timestamping code cleaner and redefine SO_TIMESTAMP* */
 #ifndef SO_TIMESTAMP
@@ -402,9 +404,10 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 			WSAGetLastError() == WSAENOBUFS ||
 			WSAGetLastError() == WSAEWOULDBLOCK) {
 #endif
+			int retries = 0;
 			/* if we set the fd blocking, other threads suddenly
 			 * have a blocking fd that they operate on */
-			while(sent == -1 && (
+			while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
 #ifndef USE_WINSOCK
 				errno == EAGAIN || errno == EINTR ||
 #  ifdef EWOULDBLOCK
@@ -419,6 +422,13 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 #endif
 			)) {
 #if defined(HAVE_POLL) || defined(USE_WINSOCK)
+				int send_nobufs = (
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+				);
 				struct pollfd p;
 				int pret;
 				memset(&p, 0, sizeof(p));
@@ -457,8 +467,48 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 					log_err("poll udp out failed: %s",
 						sock_strerror(errno));
 					return 0;
+				} else if((pret < 0 &&
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+					) || (send_nobufs && retries > 0)) {
+					/* ENOBUFS, and poll returned without
+					 * a timeout. Or the retried send call
+					 * returned ENOBUFS. It is good to
+					 * wait a bit for the error to clear. */
+					/* The timeout is 20*(2^(retries+1)),
+					 * it increases exponentially, starting
+					 * at 40 msec. After 5 tries, 1240 msec
+					 * have passed in total, when poll
+					 * returned the error, and 1200 msec
+					 * when send returned the errors. */
+#ifndef USE_WINSOCK
+					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#else
+					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#endif
+					if(pret < 0 &&
+#ifndef USE_WINSOCK
+						errno != EAGAIN && errno != EINTR &&
+#  ifdef EWOULDBLOCK
+						errno != EWOULDBLOCK &&
+#  endif
+						errno != ENOBUFS
+#else
+						WSAGetLastError() != WSAEINPROGRESS &&
+						WSAGetLastError() != WSAEINTR &&
+						WSAGetLastError() != WSAENOBUFS &&
+						WSAGetLastError() != WSAEWOULDBLOCK
+#endif
+					) {
+						log_err("poll udp out timer failed: %s",
+							sock_strerror(errno));
+					}
 				}
 #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
+				retries++;
 				if (!is_connected) {
 					sent = sendto(c->fd, (void*)sldns_buffer_begin(packet),
 						sldns_buffer_remaining(packet), 0,
@@ -665,7 +715,8 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 			WSAGetLastError() == WSAENOBUFS ||
 			WSAGetLastError() == WSAEWOULDBLOCK) {
 #endif
-			while(sent == -1 && (
+			int retries = 0;
+			while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
 #ifndef USE_WINSOCK
 				errno == EAGAIN || errno == EINTR ||
 #  ifdef EWOULDBLOCK
@@ -680,6 +731,13 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 #endif
 			)) {
 #if defined(HAVE_POLL) || defined(USE_WINSOCK)
+				int send_nobufs = (
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+				);
 				struct pollfd p;
 				int pret;
 				memset(&p, 0, sizeof(p));
@@ -718,8 +776,48 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 					log_err("poll udp out failed: %s",
 						sock_strerror(errno));
 					return 0;
+				} else if((pret < 0 &&
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+					) || (send_nobufs && retries > 0)) {
+					/* ENOBUFS, and poll returned without
+					 * a timeout. Or the retried send call
+					 * returned ENOBUFS. It is good to
+					 * wait a bit for the error to clear. */
+					/* The timeout is 20*(2^(retries+1)),
+					 * it increases exponentially, starting
+					 * at 40 msec. After 5 tries, 1240 msec
+					 * have passed in total, when poll
+					 * returned the error, and 1200 msec
+					 * when send returned the errors. */
+#ifndef USE_WINSOCK
+					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#else
+					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#endif
+					if(pret < 0 &&
+#ifndef USE_WINSOCK
+						errno != EAGAIN && errno != EINTR &&
+#  ifdef EWOULDBLOCK
+						errno != EWOULDBLOCK &&
+#  endif
+						errno != ENOBUFS
+#else
+						WSAGetLastError() != WSAEINPROGRESS &&
+						WSAGetLastError() != WSAEINTR &&
+						WSAGetLastError() != WSAENOBUFS &&
+						WSAGetLastError() != WSAEWOULDBLOCK
+#endif
+					) {
+						log_err("poll udp out timer failed: %s",
+							sock_strerror(errno));
+					}
 				}
 #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
+				retries++;
 				sent = sendmsg(c->fd, &msg, 0);
 			}
 		}