Implement support for reading and writing IPv6 flow labels in QuicUdpSocket.

Protected by FLAGS_quic_reloadable_flag_quic_support_flow_label.

PiperOrigin-RevId: 684868287
diff --git a/build/source_list.bzl b/build/source_list.bzl
index b663445..5aeac68 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1311,6 +1311,7 @@
     "quic/core/quic_time_test.cc",
     "quic/core/quic_time_wait_list_manager_test.cc",
     "quic/core/quic_trace_visitor_test.cc",
+    "quic/core/quic_udp_socket_test.cc",
     "quic/core/quic_unacked_packet_map_test.cc",
     "quic/core/quic_utils_test.cc",
     "quic/core/quic_version_manager_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 35732c7..ce85b0e 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1312,6 +1312,7 @@
     "src/quiche/quic/core/quic_time_test.cc",
     "src/quiche/quic/core/quic_time_wait_list_manager_test.cc",
     "src/quiche/quic/core/quic_trace_visitor_test.cc",
+    "src/quiche/quic/core/quic_udp_socket_test.cc",
     "src/quiche/quic/core/quic_unacked_packet_map_test.cc",
     "src/quiche/quic/core/quic_utils_test.cc",
     "src/quiche/quic/core/quic_version_manager_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 1f6ca08..c10a0a9 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1311,6 +1311,7 @@
     "quiche/quic/core/quic_time_test.cc",
     "quiche/quic/core/quic_time_wait_list_manager_test.cc",
     "quiche/quic/core/quic_trace_visitor_test.cc",
+    "quiche/quic/core/quic_udp_socket_test.cc",
     "quiche/quic/core/quic_unacked_packet_map_test.cc",
     "quiche/quic/core/quic_utils_test.cc",
     "quiche/quic/core/quic_version_manager_test.cc",
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index fd1fd04..2235d21 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -49,6 +49,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, false, true, "If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_stop_reading_also_stops_header_decompression, true, true, "If true, QUIC stream will not continue decompressing buffer headers after StopReading() called.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_support_flow_label, false, false, "If true, QUIC will support reading and writing IPv6 flow labels.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_test_peer_addr_change_after_normalize, false, false, "If true, QuicConnection::ProcessValidatedPacket will use normalized address to test peer address changes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_false, false, false, "A testonly reloadable flag that will always default to false.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_true, true, true, "A testonly reloadable flag that will always default to true.")
diff --git a/quiche/quic/core/quic_udp_socket.h b/quiche/quic/core/quic_udp_socket.h
index 404563f..001bc85 100644
--- a/quiche/quic/core/quic_udp_socket.h
+++ b/quiche/quic/core/quic_udp_socket.h
@@ -36,6 +36,7 @@
   ECN,                   // Read
   GOOGLE_PACKET_HEADER,  // Read
   IS_GRO,                // Read
+  V6_FLOW_LABEL,         // Read & Write
 
   // Must be the last value.
   NUM_BITS
@@ -154,13 +155,26 @@
     bitmask_.Set(QuicUdpPacketInfoBit::GOOGLE_PACKET_HEADER);
   }
 
-  QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; }
+  QuicEcnCodepoint ecn_codepoint() const {
+    QUICHE_DCHECK(HasValue(QuicUdpPacketInfoBit::ECN));
+    return ecn_codepoint_;
+  }
 
   void SetEcnCodepoint(const QuicEcnCodepoint ecn_codepoint) {
     ecn_codepoint_ = ecn_codepoint;
     bitmask_.Set(QuicUdpPacketInfoBit::ECN);
   }
 
+  uint32_t flow_label() const {
+    QUICHE_DCHECK(HasValue(QuicUdpPacketInfoBit::V6_FLOW_LABEL));
+    return ipv6_flow_label_;
+  }
+
+  void SetFlowLabel(uint32_t ipv6_flow_label) {
+    ipv6_flow_label_ = ipv6_flow_label;
+    bitmask_.Set(QuicUdpPacketInfoBit::V6_FLOW_LABEL);
+  }
+
  private:
   QuicUdpPacketInfoBitMask bitmask_;
   QuicPacketCount dropped_packets_;
@@ -172,6 +186,7 @@
   BufferSpan google_packet_headers_;
   size_t gso_size_ = 0;
   QuicEcnCodepoint ecn_codepoint_ = ECN_NOT_ECT;
+  uint32_t ipv6_flow_label_ = 0;
 };
 
 // QuicUdpSocketApi provides a minimal set of apis for sending and receiving
diff --git a/quiche/quic/core/quic_udp_socket_posix.inc b/quiche/quic/core/quic_udp_socket_posix.inc
index 9293bf2..0b8d7b6 100644
--- a/quiche/quic/core/quic_udp_socket_posix.inc
+++ b/quiche/quic/core/quic_udp_socket_posix.inc
@@ -10,6 +10,15 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 
+#if defined(__linux__)
+#include <linux/in6.h>
+#ifndef IPV6_FLOWLABEL
+#define IPV6_FLOWINFO 11
+#define IPV6_FLOWINFO_SEND 33
+#define IPV6_FLOWINFO_FLOWLABEL 0x000fffff
+#endif
+#endif
+
 #include "absl/base/optimization.h"
 #include "quiche/quic/core/io/socket.h"
 #include "quiche/quic/core/quic_udp_socket.h"
@@ -145,6 +154,17 @@
     }
   }
 
+#if defined(__linux__)
+  if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_FLOWINFO) {
+    if (packet_info_interested.IsSet(QuicUdpPacketInfoBit::V6_FLOW_LABEL)) {
+      uint32_t flow_label = 0;
+      memcpy(&flow_label, CMSG_DATA(cmsg), sizeof(flow_label));
+      flow_label = IPV6_FLOWINFO_FLOWLABEL & ntohl(flow_label);
+      packet_info->SetFlowLabel(flow_label);
+    }
+  }
+#endif
+
   if (packet_info_interested.IsSet(
           QuicUdpPacketInfoBit::GOOGLE_PACKET_HEADER)) {
     BufferSpan google_packet_headers;
@@ -223,6 +243,23 @@
                                    << "IPv6 socket";
       return false;
     }
+#if defined(__linux__)
+    if (GetQuicReloadableFlag(quic_support_flow_label)) {
+      if (setsockopt(fd, SOL_IPV6, IPV6_FLOWINFO, &set, sizeof(set)) != 0) {
+        QUIC_LOG_FIRST_N(ERROR, 100)
+            << "Failed to request to receive flow label on "
+            << "IPv6 socket";
+        return false;
+      }
+      if (setsockopt(fd, SOL_IPV6, IPV6_FLOWINFO_SEND, &set, sizeof(set)) !=
+          0) {
+        QUIC_LOG_FIRST_N(ERROR, 100)
+            << "Failed to request to send flow label on "
+            << "IPv6 socket";
+        return false;
+      }
+    }
+#endif
     if (!EnableReceiveSelfIpAddressForV6(fd)) {
       QUIC_LOG_FIRST_N(ERROR, 100)
           << "Failed to enable receiving of self v6 ip";
@@ -488,6 +525,20 @@
 
   cmsghdr* cmsg = nullptr;
 
+  // Set IPv6 flow label.
+#if defined(__linux__)
+  if (packet_info.HasValue(QuicUdpPacketInfoBit::V6_FLOW_LABEL) &&
+      packet_info.flow_label() != 0) {
+    uint32_t flowlabel = packet_info.flow_label();
+    if (!NextCmsg(&hdr, control_buffer, sizeof(control_buffer), SOL_IPV6,
+                  IPV6_FLOWINFO, sizeof(flowlabel), &cmsg)) {
+      QUIC_LOG_FIRST_N(ERROR, 100) << "Not enough buffer to set flow label.";
+      return WriteResult(WRITE_STATUS_ERROR, EINVAL);
+    }
+    *reinterpret_cast<uint32_t*>(CMSG_DATA(cmsg)) =
+        htonl(flowlabel & IPV6_FLOWINFO_FLOWLABEL);
+  }
+#endif
   // Set self IP.
   if (packet_info.HasValue(QuicUdpPacketInfoBit::V4_SELF_IP) &&
       packet_info.self_v4_ip().IsInitialized()) {
diff --git a/quiche/quic/core/quic_udp_socket_test.cc b/quiche/quic/core/quic_udp_socket_test.cc
new file mode 100644
index 0000000..3d46f87
--- /dev/null
+++ b/quiche/quic/core/quic_udp_socket_test.cc
@@ -0,0 +1,136 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/quic_udp_socket.h"
+
+#include <netinet/in.h>
+#include <stdint.h>
+
+#include <cstddef>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "quiche/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+const int kReceiveBufferSize = 16000;
+const int kSendBufferSize = 16000;
+
+class QuicUdpSocketTest : public QuicTest {
+ protected:
+  ABSL_CACHELINE_ALIGNED char packet_buffer_[20];
+  ABSL_CACHELINE_ALIGNED char control_buffer_[512];
+};
+
+TEST_F(QuicUdpSocketTest, Basic) {
+  SetQuicReloadableFlag(quic_support_flow_label, true);
+  const QuicSocketAddress any_address(quiche::QuicheIpAddress::Any6(), 0);
+  QuicUdpSocketApi socket_api;
+
+  SocketFd server_socket =
+      socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize);
+  ASSERT_NE(kQuicInvalidSocketFd, server_socket);
+  ASSERT_TRUE(socket_api.Bind(server_socket, any_address));
+  QuicSocketAddress server_address;
+  ASSERT_EQ(0, server_address.FromSocket(server_socket));
+
+  SocketFd client_socket =
+      socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize);
+  ASSERT_NE(kQuicInvalidSocketFd, client_socket);
+  ASSERT_TRUE(socket_api.Bind(client_socket, any_address));
+  QuicSocketAddress client_address;
+  ASSERT_EQ(0, client_address.FromSocket(client_socket));
+
+  QuicUdpPacketInfo packet_info;
+  packet_info.SetPeerAddress(server_address);
+
+  WriteResult write_result;
+  const absl::string_view client_data = "acd";
+  write_result = socket_api.WritePacket(client_socket, client_data.data(),
+                                        client_data.length(), packet_info);
+  ASSERT_EQ(WRITE_STATUS_OK, write_result.status);
+
+  QuicUdpPacketInfoBitMask packet_info_interested;
+  QuicUdpSocketApi::ReadPacketResult read_result;
+  read_result.packet_buffer = {&packet_buffer_[0], sizeof(packet_buffer_)};
+  read_result.control_buffer = {&control_buffer_[0], sizeof(control_buffer_)};
+
+  socket_api.ReadPacket(server_socket, packet_info_interested, &read_result);
+  ASSERT_TRUE(read_result.ok);
+  ASSERT_EQ(client_data,
+            absl::string_view(read_result.packet_buffer.buffer,
+                              read_result.packet_buffer.buffer_len));
+
+  const absl::string_view server_data = "acd";
+  packet_info.SetPeerAddress(client_address);
+  write_result = socket_api.WritePacket(server_socket, server_data.data(),
+                                        server_data.length(), packet_info);
+  ASSERT_EQ(WRITE_STATUS_OK, write_result.status);
+
+  read_result.Reset(sizeof(packet_buffer_));
+  socket_api.ReadPacket(client_socket, packet_info_interested, &read_result);
+  ASSERT_TRUE(read_result.ok);
+  ASSERT_EQ(server_data,
+            absl::string_view(read_result.packet_buffer.buffer,
+                              read_result.packet_buffer.buffer_len));
+}
+
+TEST_F(QuicUdpSocketTest, FlowLabel) {
+  SetQuicReloadableFlag(quic_support_flow_label, true);
+  const QuicSocketAddress any_address(quiche::QuicheIpAddress::Any6(), 0);
+  QuicUdpSocketApi socket_api;
+
+  SocketFd server_socket =
+      socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize);
+  ASSERT_NE(kQuicInvalidSocketFd, server_socket);
+  ASSERT_TRUE(socket_api.Bind(server_socket, any_address));
+  QuicSocketAddress server_address;
+  ASSERT_EQ(0, server_address.FromSocket(server_socket));
+
+  SocketFd client_socket =
+      socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize);
+  ASSERT_NE(kQuicInvalidSocketFd, client_socket);
+  ASSERT_TRUE(socket_api.Bind(client_socket, any_address));
+  QuicSocketAddress client_address;
+  ASSERT_EQ(0, client_address.FromSocket(client_socket));
+
+  const absl::string_view data = "a";
+  const uint32_t client_flow_label = 1;
+  QuicUdpPacketInfo packet_info;
+  packet_info.SetFlowLabel(client_flow_label);
+  packet_info.SetPeerAddress(server_address);
+
+  WriteResult write_result;
+  write_result = socket_api.WritePacket(client_socket, data.data(),
+                                        data.length(), packet_info);
+  ASSERT_EQ(WRITE_STATUS_OK, write_result.status);
+
+  QuicUdpPacketInfoBitMask packet_info_interested(
+      {quic::QuicUdpPacketInfoBit::V6_FLOW_LABEL});
+  QuicUdpSocketApi::ReadPacketResult read_result;
+  read_result.packet_buffer = {&packet_buffer_[0], sizeof(packet_buffer_)};
+  read_result.control_buffer = {&control_buffer_[0], sizeof(control_buffer_)};
+
+  socket_api.ReadPacket(server_socket, packet_info_interested, &read_result);
+  EXPECT_EQ(client_flow_label, read_result.packet_info.flow_label());
+
+  const uint32_t server_flow_label = 3;
+  packet_info.SetPeerAddress(client_address);
+  packet_info.SetFlowLabel(server_flow_label);
+  write_result = socket_api.WritePacket(server_socket, data.data(),
+                                        data.length(), packet_info);
+  ASSERT_EQ(WRITE_STATUS_OK, write_result.status);
+
+  read_result.Reset(sizeof(packet_buffer_));
+  socket_api.ReadPacket(client_socket, packet_info_interested, &read_result);
+  EXPECT_EQ(server_flow_label, read_result.packet_info.flow_label());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic