QUIC sockets return WRITE_STATUS_BLOCKED on ENOBUFS.

Protected by FLAGS_quic_reloadable_flag_quic_enobufs_blocekd.

PiperOrigin-RevId: 765182479
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 09e6b2a..de37bdf 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -34,6 +34,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_mtu_discovery_at_server, false, false, "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_server_on_wire_ping, true, true, "If true, enable server retransmittable on wire PING.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_version_rfcv2, false, false, "When true, support RFC9369.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enobufs_blocked, false, false, "If true, ENOBUFS socket errors are reported as socket blocked instead of socket failure.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_fin_before_completed_http_headers, false, true, "If true, close the connection with error if FIN is received before finish receiving the whole HTTP headers.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_fix_timeouts, true, true, "If true, postpone setting handshake timeout to infinite to handshake complete.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_heapless_key_derivation, false, false, "If true, QUIC key derivation uses heapless crypto utils.")
diff --git a/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc b/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
index 1d5a80f..6e0574d 100644
--- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
+++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
@@ -12,7 +12,7 @@
 
 #include "quiche/quic/core/flow_label.h"
 #include "quiche/quic/core/quic_linux_socket_utils.h"
-#include "quiche/quic/platform/api/quic_server_stats.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 
 namespace quic {
 
@@ -32,7 +32,8 @@
       supports_release_time_(
           GetQuicRestartFlag(quic_support_release_time_for_gso) &&
           QuicLinuxSocketUtils::EnableReleaseTime(fd,
-                                                  clockid_for_release_time)) {
+                                                  clockid_for_release_time)),
+      enobufs_blocked_(GetQuicReloadableFlag(quic_enobufs_blocked)) {
   if (supports_release_time_) {
     QUIC_RESTART_FLAG_COUNT(quic_support_release_time_for_gso);
   }
diff --git a/quiche/quic/core/batch_writer/quic_gso_batch_writer.h b/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
index 5e82952..b2d560f 100644
--- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
+++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
@@ -85,7 +85,8 @@
     cmsg_builder(&hdr, first.self_address, gso_size, first.release_time,
                  first.params.ecn_codepoint, first.params.flow_label);
 
-    write_result = QuicLinuxSocketUtils::WritePacket(fd(), hdr);
+    write_result =
+        QuicLinuxSocketUtils::WritePacket(fd(), hdr, enobufs_blocked_);
     QUIC_DVLOG(1) << "Write GSO packet result: " << write_result
                   << ", fd: " << fd()
                   << ", self_address: " << first.self_address.ToString()
@@ -118,6 +119,8 @@
 
   const clockid_t clockid_for_release_time_;
   const bool supports_release_time_;
+
+  const bool enobufs_blocked_ = false;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_linux_socket_utils.cc b/quiche/quic/core/quic_linux_socket_utils.cc
index 8134eea..c56d939 100644
--- a/quiche/quic/core/quic_linux_socket_utils.cc
+++ b/quiche/quic/core/quic_linux_socket_utils.cc
@@ -7,11 +7,13 @@
 #include <linux/net_tstamp.h>
 #include <netinet/in.h>
 
+#include <cerrno>
 #include <cstddef>
 #include <cstdint>
 #include <string>
 
 #include "quiche/quic/core/quic_syscall_wrapper.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
 #include "quiche/quic/platform/api/quic_ip_address.h"
 #include "quiche/quic/platform/api/quic_logging.h"
@@ -263,7 +265,8 @@
 }
 
 // static
-WriteResult QuicLinuxSocketUtils::WritePacket(int fd, const QuicMsgHdr& hdr) {
+WriteResult QuicLinuxSocketUtils::WritePacket(int fd, const QuicMsgHdr& hdr,
+                                              bool enobufs_blocked) {
   int rc;
   do {
     rc = GetGlobalSyscallWrapper()->Sendmsg(fd, hdr.hdr(), 0);
@@ -273,7 +276,11 @@
   }
   if (errno == ENOBUFS) {
     QUIC_CODE_COUNT(quic_sendmsg_enobufs);
-    errno = ENOBUFS;
+    if (enobufs_blocked) {
+      return WriteResult(WRITE_STATUS_BLOCKED, errno);
+    } else {
+      errno = ENOBUFS;
+    }
   }
   return WriteResult((errno == EAGAIN || errno == EWOULDBLOCK)
                          ? WRITE_STATUS_BLOCKED
diff --git a/quiche/quic/core/quic_linux_socket_utils.h b/quiche/quic/core/quic_linux_socket_utils.h
index f125cba..1c0b86d 100644
--- a/quiche/quic/core/quic_linux_socket_utils.h
+++ b/quiche/quic/core/quic_linux_socket_utils.h
@@ -281,7 +281,8 @@
                                 cmsghdr* cmsg);
 
   // Writes the packet in |hdr| to the socket, using ::sendmsg.
-  static WriteResult WritePacket(int fd, const QuicMsgHdr& hdr);
+  static WriteResult WritePacket(int fd, const QuicMsgHdr& hdr,
+                                 bool enobufs_blocked);
 
   // Writes the packets in |mhdr| to the socket, using ::sendmmsg if available.
   static WriteResult WriteMultiplePackets(int fd, QuicMMsgHdr* mhdr,
diff --git a/quiche/quic/core/quic_linux_socket_utils_test.cc b/quiche/quic/core/quic_linux_socket_utils_test.cc
index 93ebf01..1a910fe 100644
--- a/quiche/quic/core/quic_linux_socket_utils_test.cc
+++ b/quiche/quic/core/quic_linux_socket_utils_test.cc
@@ -7,11 +7,13 @@
 #include <netinet/in.h>
 #include <stdint.h>
 
+#include <cerrno>
 #include <cstddef>
 #include <sstream>
 #include <string>
 #include <vector>
 
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/quic_mock_syscall_wrapper.h"
 #include "quiche/common/quiche_circular_deque.h"
@@ -19,6 +21,7 @@
 using testing::_;
 using testing::InSequence;
 using testing::Invoke;
+using testing::SetErrnoAndReturn;
 
 namespace quic {
 namespace test {
@@ -323,6 +326,16 @@
   }
 }
 
+TEST_F(QuicLinuxSocketUtilsTest, WriteReturnsEnobufs) {
+  QuicMsgHdr hdr(nullptr, 0, nullptr, 0);
+  EXPECT_CALL(mock_syscalls_, Sendmsg)
+      .WillRepeatedly(SetErrnoAndReturn(ENOBUFS, -1));
+  EXPECT_EQ(WriteResult(WRITE_STATUS_BLOCKED, ENOBUFS),
+            QuicLinuxSocketUtils::WritePacket(/*fd=*/1, hdr, true));
+  EXPECT_EQ(WriteResult(WRITE_STATUS_ERROR, ENOBUFS),
+            QuicLinuxSocketUtils::WritePacket(/*fd=*/1, hdr, false));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic